Skip to content

fix(react): isolate loader data from activity context#736

Open
ENvironmentSet wants to merge 4 commits into
mainfrom
feature/fep-2613
Open

fix(react): isolate loader data from activity context#736
ENvironmentSet wants to merge 4 commits into
mainfrom
feature/fep-2613

Conversation

@ENvironmentSet

Copy link
Copy Markdown
Collaborator

Problem

The built-in loaderPlugin in @stackflow/react stores loader results (as Promises) in the shared activityContext.loaderData. Since activityContext is shared across all plugins and is included verbatim in StackSnapshot events:

  • There is no per-plugin key ownership/namespace, so the loaderData key can collide with other plugins' or users' data and be silently overwritten depending on execution order.
  • Runtime-only data (Promises, loader return values) leaks into Snapshots, breaking the plain-data / round-trip contract of StackSnapshot.

Solution

Loader runtime state now lives in plugin-owned storage, fully outside activityContext:

  • Each core-store loaderPlugin instance owns private Maps keyed by the entering domain-event ID, isolating multiple Stackflow/SSR store instances and keeping separate loader generations when replace() reuses an activity ID (including a rendered generation plus a queued paused replacement).
  • wrapActivity provides the promise for activity.enteredBy.id to useLoaderData through an internal React Context — no render-phase mutation, no effects.
  • Lifecycle cleanup drops exit-done generations, retains queued paused generations, and removes staged data for prevented actions.
  • loaderPlugin no longer reads or writes activityContext.loaderData; values placed there by users or other plugins pass through unchanged (and are no longer consumed by useLoaderData).

No @stackflow/core public contract changed. Existing public surfaces are preserved: useLoaderData, SSR initialLoaderData, core v3 load (restore) path with loader re-execution and deferred resolution, pausedEvents handling, pause/resume loading behavior, and the lazy shouldRenderImmediately path.

Verification

  • yarn typecheck (all workspaces), @stackflow/react / @stackflow/plugin-stack-persistence / demo builds — PASS
  • Existing suites: @stackflow/core (14 suites / 84 tests), @stackflow/plugin-history-sync (7 suites / 52 tests) — PASS
  • Runtime harness covering: fresh loader + real useLoaderData render, foreign-key collision isolation, Snapshot JSON safety, core v3 restore round-trip, SSR initialLoaderData, plugin-stack-persistence save/load round-trip, paused same-ID replacement generations, pending loader + lazy pause/resume, independent stores reusing identical IDs, exit-done / prevented-action cleanup — all PASS. The harness rejects any Promise found in captured Snapshot records and confirms deliberately-planted foreign activityContext.loaderData values are preserved for core/persistence while useLoaderData returns only the plugin-owned value.

Closes FEP-2613

🤖 Generated with Claude Code

https://claude.ai/code/session_01EYjxc3pkrpHin2PhoVQABq

@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 63fd197

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@stackflow/react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 40511a18-dc01-4960-8866-8721d264e820

📥 Commits

Reviewing files that changed from the base of the PR and between 87286ba and 63fd197.

📒 Files selected for processing (1)
  • integrations/react/src/loader/loaderPlugin.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • integrations/react/src/loader/loaderPlugin.tsx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved loader data handling across navigation, activity transitions, and page lifecycle changes.
    • Prevented loader promises and returned values from being retained in activity context or captured navigation snapshots.
    • Kept resolved loader data accessible via useLoaderData, including deferred and async loader scenarios.
  • New Features
    • Added a dedicated loader-data React context with a useLoaderDataPromise accessor.
  • Chores
    • Added a patch release note documenting the updated loader data behavior.

Walkthrough

Loader results move from activity context into loader-plugin-owned maps and are exposed through a React context. Initialization, restoration, navigation preloading, promotion, and cleanup now use event- and activity-keyed runtime state.

Changes

Loader data isolation

Layer / File(s) Summary
Loader data context contract
integrations/react/src/loader/LoaderDataContext.tsx, integrations/react/src/loader/useLoaderData.ts
Adds a context-backed loader promise provider and updates useLoaderData to resolve loader data from that context.
Event-keyed loader state
integrations/react/src/loader/loaderPlugin.tsx
Stores loader promises and deferreds by event or activity identifier, provides data through LoaderDataProvider, and updates initial and restored-stack resolution.
Navigation staging and cleanup
integrations/react/src/loader/loaderPlugin.tsx, .changeset/fep-2613-isolate-loader-data.md
Stages preloaded data in runtime state, pauses and resumes navigation actions, promotes retained data, cleans stale entries, and records the patch release.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RouteNavigation
  participant loaderPlugin
  participant LoaderDataProvider
  participant Activity
  RouteNavigation->>loaderPlugin: Start route preload
  loaderPlugin->>loaderPlugin: Pause actions
  loaderPlugin->>loaderPlugin: Store resolved loader data
  loaderPlugin->>loaderPlugin: Resume actions
  loaderPlugin->>LoaderDataProvider: Provide loader promise
  LoaderDataProvider->>Activity: Render activity with loader context
  loaderPlugin->>loaderPlugin: Remove staged data on next microtask
Loading

Possibly related PRs

  • daangn/stackflow#732: Both update loader-plugin deferred resolution and load-path handling for snapshot restoration.
  • daangn/stackflow#738: Both modify createBeforeRouteHandler and runtime loader-data staging in loaderPlugin.tsx.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: isolating loader data from activity context.
Description check ✅ Passed The description accurately describes the PR's purpose and the loader-state isolation changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fep-2613

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown
@stackflow/link

yarn add https://pkg.pr.new/@stackflow/link@736.tgz

@stackflow/plugin-basic-ui

yarn add https://pkg.pr.new/@stackflow/plugin-basic-ui@736.tgz

@stackflow/plugin-blocker

yarn add https://pkg.pr.new/@stackflow/plugin-blocker@736.tgz

@stackflow/plugin-google-analytics-4

yarn add https://pkg.pr.new/@stackflow/plugin-google-analytics-4@736.tgz

@stackflow/plugin-history-sync

yarn add https://pkg.pr.new/@stackflow/plugin-history-sync@736.tgz

@stackflow/plugin-lifecycle

yarn add https://pkg.pr.new/@stackflow/plugin-lifecycle@736.tgz

@stackflow/plugin-renderer-basic

yarn add https://pkg.pr.new/@stackflow/plugin-renderer-basic@736.tgz

@stackflow/plugin-renderer-web

yarn add https://pkg.pr.new/@stackflow/plugin-renderer-web@736.tgz

@stackflow/react-ui-core

yarn add https://pkg.pr.new/@stackflow/react-ui-core@736.tgz

@stackflow/react

yarn add https://pkg.pr.new/@stackflow/react@736.tgz

commit: 63fd197

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploying stackflow-demo with  Cloudflare Pages  Cloudflare Pages

Latest commit: 63fd197
Status: ✅  Deploy successful!
Preview URL: https://9a925eec.stackflow-demo.pages.dev
Branch Preview URL: https://feature-fep-2613.stackflow-demo.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
stackflow-docs 63fd197 Commit Preview URL Jul 24 2026, 09:49 AM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant