diff --git a/.changeset/fep-2635-observe-prevented-actions.md b/.changeset/fep-2635-observe-prevented-actions.md new file mode 100644 index 000000000..3605281fb --- /dev/null +++ b/.changeset/fep-2635-observe-prevented-actions.md @@ -0,0 +1,5 @@ +--- +"@stackflow/core": minor +--- + +Expose the live, action-local `actions.isPrevented()` state to every pre-effect hook without stopping the remaining hooks. diff --git a/.changeset/fep-2635-skip-prevented-loader-work.md b/.changeset/fep-2635-skip-prevented-loader-work.md new file mode 100644 index 000000000..b020735bc --- /dev/null +++ b/.changeset/fep-2635-skip-prevented-loader-work.md @@ -0,0 +1,5 @@ +--- +"@stackflow/react": patch +--- + +Require `@stackflow/core` v3 and skip loader, preload, and pause work for prevented push and replace actions. diff --git a/core/src/interfaces/StackflowPluginHook.ts b/core/src/interfaces/StackflowPluginHook.ts index f2656ef9a..8c3853844 100644 --- a/core/src/interfaces/StackflowPluginHook.ts +++ b/core/src/interfaces/StackflowPluginHook.ts @@ -18,6 +18,7 @@ export type StackflowPluginInitHook = (args: { export type StackflowPluginPreEffectHook = (args: { actionParams: T; actions: StackflowActions & { + isPrevented: () => boolean; preventDefault: () => void; overrideActionParams: (params: T) => void; }; diff --git a/core/src/utils/triggerPreEffectHooks.ts b/core/src/utils/triggerPreEffectHooks.ts index 1e0433d3b..a82223c95 100644 --- a/core/src/utils/triggerPreEffectHooks.ts +++ b/core/src/utils/triggerPreEffectHooks.ts @@ -47,6 +47,7 @@ export function triggerPreEffectHook( actionParams: { ...nextActionParams }, actions: { ...actions, + isPrevented: () => isPrevented, preventDefault: () => { isPrevented = true; }, diff --git a/docs/pages/docs/advanced/write-plugin.en.mdx b/docs/pages/docs/advanced/write-plugin.en.mdx index 7f6582b1a..3aee29605 100644 --- a/docs/pages/docs/advanced/write-plugin.en.mdx +++ b/docs/pages/docs/advanced/write-plugin.en.mdx @@ -251,11 +251,14 @@ Pre-effect hooks include `onBeforePush`, `onBeforeReplace`, and `onBeforePop`. P | | | | | ---------------------- | ---------- | ------------------------------------------ | | actions.preventDefault | `function` | Cancel the default behavior. | +| actions.isPrevented | `function` | Check whether the action is prevented. | | actions.getStack | `function` | Get the current stack state. | | actions.dispatchEvent | `function` | Add a new event to the core. | | effect | `object` | The effect that triggered the effect hook. | +Calling `preventDefault()` cancels the default event dispatch, but it does not stop the remaining pre-effect hooks. Those hooks can call `isPrevented()` to observe the current action-local state. + ## Determining initial activity You can override the existing `initialActivity` behavior through the `overrideInitialEvents` API. diff --git a/docs/pages/docs/advanced/write-plugin.ko.mdx b/docs/pages/docs/advanced/write-plugin.ko.mdx index 73ba436d0..456c52bd9 100644 --- a/docs/pages/docs/advanced/write-plugin.ko.mdx +++ b/docs/pages/docs/advanced/write-plugin.ko.mdx @@ -259,11 +259,14 @@ Pre-effect 훅에는 `onBeforePush`, `onBeforeReplace`, `onBeforePop`이 있어 | | | | | ---------------------- | ---------- | ----------------------------- | | actions.preventDefault | `function` | (추가) 기본 동작을 취소해요. | +| actions.isPrevented | `function` | 현재 action의 취소 상태를 확인해요. | | actions.getStack | `function` | 현재 스택의 상태를 가져올 수 있어요. | | actions.dispatchEvent | `function` | 코어에 새 이벤트를 추가해요. | | effect | `object` | 해당 이펙트 훅을 촉발시킨 이펙트에요. | +`preventDefault()`를 호출하면 기본 이벤트 dispatch는 취소되지만 남은 pre-effect 훅은 계속 실행돼요. 후속 훅은 `isPrevented()`를 호출해 현재 action에 한정된 취소 상태를 확인할 수 있어요. + ## 첫 액티비티 결정하기 `overrideInitialEvents` API를 통해 기존에 존재하는 `initialActivity` 동작을 덮어쓸 수 있어요. diff --git a/integrations/react/package.json b/integrations/react/package.json index a111cfa2c..01c80ee6f 100644 --- a/integrations/react/package.json +++ b/integrations/react/package.json @@ -46,7 +46,7 @@ }, "peerDependencies": { "@stackflow/config": "^2.0.0", - "@stackflow/core": "^2.0.0 || ^3.0.0", + "@stackflow/core": "^3.0.0", "@types/react": ">=16.8.0", "react": ">=16.8.0" }, diff --git a/integrations/react/src/loader/loaderPlugin.tsx b/integrations/react/src/loader/loaderPlugin.tsx index 59087dd0c..a06623cfe 100644 --- a/integrations/react/src/loader/loaderPlugin.tsx +++ b/integrations/react/src/loader/loaderPlugin.tsx @@ -206,10 +206,12 @@ function createBeforeRouteHandler< input: StackflowInput, loadData: (activityName: string, activityParams: {}) => unknown, ): OnBeforeRoute { - return ({ - actionParams, - actions: { overrideActionParams, pause, resume }, - }) => { + return ({ actionParams, actions }) => { + if (actions.isPrevented()) { + return; + } + + const { overrideActionParams, pause, resume } = actions; const { activityName, activityParams, activityContext } = actionParams; const matchActivity = input.config.activities.find( diff --git a/yarn.lock b/yarn.lock index f14bc9ec6..8dd887e02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6068,7 +6068,7 @@ __metadata: typescript: "npm:^5.5.3" peerDependencies: "@stackflow/config": ^2.0.0 - "@stackflow/core": ^2.0.0 || ^3.0.0 + "@stackflow/core": ^3.0.0 "@types/react": ">=16.8.0" react: ">=16.8.0" languageName: unknown