Skip to content

Commit 4b894ae

Browse files
feat(react): Make structured activities defined by other than the standard constructor can be preloaded and cached (#674)
1 parent 57fd2da commit 4b894ae

File tree

6 files changed

+35
-29
lines changed

6 files changed

+35
-29
lines changed

.changeset/yellow-geckos-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stackflow/react": patch
3+
---
4+
5+
Make structured activities defined by other than the standard constructor can be preloaded and cached

integrations/react/src/__internal__/PluginRenderer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ function renderStructuredActivityComponent(
8787
params: {},
8888
): ReactNode {
8989
const { layout, loading, errorHandler } = structuredActivityComponent;
90-
const ContentComponent = getContentComponent(structuredActivityComponent);
90+
const { Component: ContentComponent } = getContentComponent(
91+
structuredActivityComponent,
92+
);
9193

9294
const wrappers: Array<(node: ReactNode) => ReactNode> = [
9395
(node) =>

integrations/react/src/__internal__/StructuredActivityComponentType.tsx

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,8 @@ export function structuredActivityComponent<
3434
loading?: Loading<InferActivityParams<ActivityName>>;
3535
errorHandler?: ErrorHandler<InferActivityParams<ActivityName>>;
3636
}): StructuredActivityComponentType<InferActivityParams<ActivityName>> {
37-
const content = options.content;
38-
let cachedContent: SyncInspectablePromise<{
39-
default: Content<InferActivityParams<ActivityName>>;
40-
}> | null = null;
41-
4237
return {
4338
...options,
44-
content:
45-
typeof content !== "function"
46-
? content
47-
: () => {
48-
if (
49-
!cachedContent ||
50-
inspect(cachedContent).status === PromiseStatus.REJECTED
51-
) {
52-
cachedContent = resolve(content());
53-
}
54-
55-
return cachedContent;
56-
},
5739
[STRUCTURED_ACTIVITY_COMPONENT_TYPE]: true,
5840
};
5941
}
@@ -80,17 +62,17 @@ export function content<ActivityName extends RegisteredActivityName>(
8062

8163
const ContentComponentMap = new WeakMap<
8264
StructuredActivityComponentType<{}>,
83-
Content<{}>["component"]
65+
{ Component: Content<{}>["component"]; preload: () => Promise<void> }
8466
>();
8567

8668
export function getContentComponent(
8769
structuredActivityComponent: StructuredActivityComponentType<{}>,
88-
): Content<{}>["component"] {
70+
): { Component: Content<{}>["component"]; preload: () => Promise<void> } {
8971
if (ContentComponentMap.has(structuredActivityComponent)) {
9072
return ContentComponentMap.get(structuredActivityComponent)!;
9173
}
9274

93-
const { Component: ContentComponent } = preloadableLazyComponent(() => {
75+
const { Component, preload } = preloadableLazyComponent(() => {
9476
const content = structuredActivityComponent.content;
9577
const contentPromise = resolve(
9678
typeof content === "function" ? content() : { default: content },
@@ -112,9 +94,9 @@ export function getContentComponent(
11294
);
11395
});
11496

115-
ContentComponentMap.set(structuredActivityComponent, ContentComponent);
97+
ContentComponentMap.set(structuredActivityComponent, { Component, preload });
11698

117-
return ContentComponent;
99+
return { Component, preload };
118100
}
119101

120102
export interface Layout<P extends {}> {

integrations/react/src/__internal__/utils/PreloadableLazyComponent.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ComponentType } from "react";
22
import {
33
inspect,
44
PromiseStatus,
5+
resolve,
56
type SyncInspectablePromise,
67
} from "./SyncInspectablePromise";
78
import { useThenable } from "./useThenable";
@@ -30,6 +31,14 @@ export function preloadableLazyComponent<P extends {}>(
3031

3132
return {
3233
Component,
33-
preload: async () => void (await cachedLoad()),
34+
preload: () => {
35+
const componentPromise = cachedLoad();
36+
37+
if (inspect(componentPromise).status === PromiseStatus.FULFILLED) {
38+
return resolve(undefined);
39+
}
40+
41+
return componentPromise.then(() => undefined);
42+
},
3443
};
3544
}

integrations/react/src/future/loader/loaderPlugin.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import type {
44
} from "@stackflow/config";
55
import type { ActivityComponentType } from "../../__internal__/ActivityComponentType";
66
import type { StackflowReactPlugin } from "../../__internal__/StackflowReactPlugin";
7-
import { isStructuredActivityComponent } from "../../__internal__/StructuredActivityComponentType";
7+
import {
8+
getContentComponent,
9+
isStructuredActivityComponent,
10+
} from "../../__internal__/StructuredActivityComponentType";
811
import { isPromiseLike } from "../../__internal__/utils/isPromiseLike";
912
import {
1013
inspect,
@@ -114,7 +117,7 @@ function createBeforeRouteHandler<
114117
const lazyComponentPromise = resolve(
115118
isStructuredActivityComponent(matchActivityComponent) &&
116119
typeof matchActivityComponent.content === "function"
117-
? matchActivityComponent.content()
120+
? getContentComponent(matchActivityComponent).preload()
118121
: "_load" in matchActivityComponent &&
119122
typeof matchActivityComponent._load === "function"
120123
? matchActivityComponent._load()

integrations/react/src/future/usePrepare.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import type {
44
} from "@stackflow/config";
55
import { useCallback } from "react";
66
import { useActivityComponentMap } from "../__internal__/ActivityComponentMapProvider";
7-
import { isStructuredActivityComponent } from "../__internal__/StructuredActivityComponentType";
7+
import {
8+
getContentComponent,
9+
isStructuredActivityComponent,
10+
} from "../__internal__/StructuredActivityComponentType";
811
import { useDataLoader } from "./loader";
912
import { useConfig } from "./useConfig";
1013

@@ -47,7 +50,9 @@ export function usePrepare(): Prepare {
4750
isStructuredActivityComponent(activityComponentMap[activityName]) &&
4851
typeof activityComponentMap[activityName].content === "function"
4952
) {
50-
prefetchTasks.push(activityComponentMap[activityName].content());
53+
prefetchTasks.push(
54+
getContentComponent(activityComponentMap[activityName]).preload(),
55+
);
5156
}
5257

5358
await Promise.all(prefetchTasks);

0 commit comments

Comments
 (0)