From ba2ed121e1f976d854d3044338beca9cc2887cc3 Mon Sep 17 00:00:00 2001 From: Tony Won <tony@daangn.com> Date: Tue, 19 Nov 2024 15:57:31 +0900 Subject: [PATCH 1/2] WIP: add `useDeferredStack` flag in `stackflow()` initialization --- .../src/__internal__/core/CoreProvider.tsx | 14 +++--------- integrations/react/src/future/stackflow.tsx | 17 +++++++++++++- integrations/react/src/stable/stackflow.tsx | 22 ++++++++++++++++++- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/integrations/react/src/__internal__/core/CoreProvider.tsx b/integrations/react/src/__internal__/core/CoreProvider.tsx index 565e5b59a..194381337 100644 --- a/integrations/react/src/__internal__/core/CoreProvider.tsx +++ b/integrations/react/src/__internal__/core/CoreProvider.tsx @@ -1,8 +1,6 @@ import type { CoreStore, Stack } from "@stackflow/core"; import { createContext } from "react"; -import { useDeferredValue, useSyncExternalStore } from "../shims"; - export const CoreActionsContext = createContext<CoreStore["actions"]>( null as any, ); @@ -10,22 +8,16 @@ export const CoreStateContext = createContext<Stack>(null as any); export interface CoreProviderProps { coreStore: CoreStore; + coreState: Stack; children: React.ReactNode; } export const CoreProvider: React.FC<CoreProviderProps> = ({ coreStore, + coreState, children, }) => { - const stack = useSyncExternalStore( - coreStore.subscribe, - coreStore.actions.getStack, - coreStore.actions.getStack, - ); - - const deferredStack = useDeferredValue(stack); - return ( - <CoreStateContext.Provider value={deferredStack}> + <CoreStateContext.Provider value={coreState}> <CoreActionsContext.Provider value={coreStore.actions}> {children} </CoreActionsContext.Provider> diff --git a/integrations/react/src/future/stackflow.tsx b/integrations/react/src/future/stackflow.tsx index 47abcd78b..94f3b182f 100644 --- a/integrations/react/src/future/stackflow.tsx +++ b/integrations/react/src/future/stackflow.tsx @@ -14,6 +14,7 @@ import MainRenderer from "../__internal__/MainRenderer"; import { makeActivityId } from "../__internal__/activity"; import { CoreProvider } from "../__internal__/core"; import { PluginsProvider } from "../__internal__/plugins"; +import { useDeferredValue, useSyncExternalStore } from "../__internal__/shims"; import { isBrowser, makeRef } from "../__internal__/utils"; import type { ActivityComponentType, StackflowReactPlugin } from "../stable"; import type { Actions } from "./Actions"; @@ -37,6 +38,7 @@ export type StackflowInput< config: Config<T>; components: R; plugins?: Array<StackflowPluginsEntry>; + useDeferredStack?: boolean; }; export type StackflowOutput = { @@ -62,6 +64,8 @@ export function stackflow< loaderPlugin(input.config), ]; + const useDeferredStack = input.useDeferredStack ?? true; + const enoughPastTime = () => new Date().getTime() - input.config.transitionDuration * 2; @@ -154,10 +158,21 @@ export function stackflow< return store; }, []); + const coreState = useSyncExternalStore( + coreStore.subscribe, + coreStore.actions.getStack, + coreStore.actions.getStack, + ); + + const deferredCoreState = useDeferredValue(coreState); + return ( <ConfigProvider value={input.config}> <PluginsProvider value={coreStore.pluginInstances}> - <CoreProvider coreStore={coreStore}> + <CoreProvider + coreState={useDeferredStack ? deferredCoreState : coreState} + coreStore={coreStore} + > <MainRenderer activityComponentMap={input.components} initialContext={initialContext} diff --git a/integrations/react/src/stable/stackflow.tsx b/integrations/react/src/stable/stackflow.tsx index 85b69d4fd..27fad60ac 100644 --- a/integrations/react/src/stable/stackflow.tsx +++ b/integrations/react/src/stable/stackflow.tsx @@ -14,6 +14,7 @@ import type { StackflowReactPlugin } from "../__internal__/StackflowReactPlugin" import { makeActivityId, makeStepId } from "../__internal__/activity"; import { CoreProvider } from "../__internal__/core"; import { PluginsProvider } from "../__internal__/plugins"; +import { useDeferredValue, useSyncExternalStore } from "../__internal__/shims"; import { isBrowser, makeRef } from "../__internal__/utils"; import type { BaseActivities } from "./BaseActivities"; import type { UseActionsOutputType } from "./useActions"; @@ -69,6 +70,12 @@ export type StackflowOptions<T extends BaseActivities> = { * Inject stackflow plugins */ plugins?: Array<StackflowPluginsEntry<NoInfer<T>>>; + + /** + * Render stack state with `useDeferredValue()` (concurrent rendering) + * https://react.dev/reference/react/useDeferredValue + */ + useDeferredStack?: boolean; }; export type StackflowOutput<T extends BaseActivities> = { @@ -135,6 +142,8 @@ export function stackflow<T extends BaseActivities>( }, ); + const useDeferredStack = options.useDeferredStack ?? true; + const enoughPastTime = () => new Date().getTime() - options.transitionDuration * 2; @@ -218,9 +227,20 @@ export function stackflow<T extends BaseActivities>( return store; }, []); + const coreState = useSyncExternalStore( + coreStore.subscribe, + coreStore.actions.getStack, + coreStore.actions.getStack, + ); + + const deferredCoreState = useDeferredValue(coreState); + return ( <PluginsProvider value={coreStore.pluginInstances}> - <CoreProvider coreStore={coreStore}> + <CoreProvider + coreStore={coreStore} + coreState={useDeferredStack ? deferredCoreState : coreState} + > <MainRenderer activityComponentMap={activityComponentMap} initialContext={props.initialContext} From ea5f499b60e6edbe96629e6a1bfcdb1342d9cc5b Mon Sep 17 00:00:00 2001 From: Tony Won <tony@daangn.com> Date: Mon, 2 Dec 2024 11:31:27 +0900 Subject: [PATCH 2/2] changeset --- .changeset/honest-gorillas-pretend.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/honest-gorillas-pretend.md diff --git a/.changeset/honest-gorillas-pretend.md b/.changeset/honest-gorillas-pretend.md new file mode 100644 index 000000000..ed28ceec4 --- /dev/null +++ b/.changeset/honest-gorillas-pretend.md @@ -0,0 +1,5 @@ +--- +"@stackflow/react": patch +--- + +feat(react): Add option `useDeferredStack` to disable use of `useDeferredValue`