Skip to content

fix: flatten providers for large state trees - #7441

Merged
FarhanAliRaza merged 23 commits into
reflex-dev:mainfrom
harsh21234i:harsh21234i-fix-large-state-tree
Oct 15, 2026
Merged

FarhanAliRaza merged 23 commits into
reflex-dev:mainfrom
harsh21234i:harsh21234i-fix-large-state-tree

Conversation

@harsh21234i

@harsh21234i harsh21234i commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7429

Summary

Flatten the state provider tree on both the client and server. A single shared state-store context keeps provider depth independent of the number of substates, including during development SSR.

Generated state reads subscribe to their substate with useSyncExternalStore. A delta applies immutably to only that substate and notifies only its subscribers, avoiding a full state-map copy or rebuilding all provider elements on every update. The external-store hook gives React consistent snapshots during concurrent rendering; the store is changed only by event dispatch, never during render.

Testing

  • Focused context, compiler, memoization, and component tests: 361 passed, 1 skipped, 1 excluded because the local environment lacks the optional Plotly dependency.
  • Includes a Node runtime test that traverses an SSR tree with 5,000 substates and a client test that verifies targeted notification and unchanged-state identity.
  • Focused Ruff checks and git diff --check passed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@harsh21234i
harsh21234i requested a review from a team as a code owner October 6, 2026 05:36
@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; the outstanding hot-refresh finding is fixed.

Summary

The PR replaces nested state providers with one shared store, keeping provider depth constant as the state tree grows.

  • State providers stay shallow as an app adds state classes.
  • State updates reach only readers of the changed substate.

Reviews (21) · Last reviewed commit: "fix: merge new default fields into live ..." · Reviewed by Greptile

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread tests/units/compiler/test_compiler.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread tests/units/compiler/test_compiler.py Outdated
@codspeed

codspeed Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 16 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 148 untouched benchmarks


Comparing harsh21234i:harsh21234i-fix-large-state-tree (3084bc4) with main (2fc4a9c)

Open in CodSpeed

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@masenf masenf added the bug Something isn't working label Oct 6, 2026

@FarhanAliRaza FarhanAliRaza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes. See the inline comments.


Generated by Claude Code

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread tests/units/compiler/test_compiler.py Outdated
@harsh21234i harsh21234i changed the title fix: reduce client state provider nesting fix: flatten providers for large state trees Oct 8, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

View guided diff | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread tests/units/components/test_component.py
Comment thread packages/reflex-base/src/reflex_base/vars/base.py

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/reflex-base/news/+large-state-provider-tree.performance.md Outdated
@masenf masenf added the on deck PRs lined up to review / merge next label Oct 9, 2026 — with ChatGPT Codex Connector
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/context-registry.js Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py
Comment thread packages/reflex-base/src/reflex_base/vars/base.py Outdated
Comment thread packages/reflex-base/src/reflex_base/vars/base.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py

Copy link
Copy Markdown
Contributor Author

The fix for #7429 replaces the per-substate providers with a shared store. The compiler rewrite preserves legacy reads in compiled Reflex code, but it cannot rewrite independently bundled third-party components using useContext(StateContexts.foo). Is that raw context access a supported compatibility surface? I’d like to confirm before adding a compatibility path that could bring back the provider-depth problem.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread reflex/compiler/compiler.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
@harsh21234i
harsh21234i requested a review from Alek99 as a code owner October 14, 2026 06:57

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
Comment thread reflex/compiler/compiler.py Outdated
@masenf

masenf commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Is that raw context access a supported compatibility surface?

No, accessing the state content directly via javacript not generated by the reflex compiler is not a supported path.

harsh21234i and others added 2 commits October 14, 2026 12:45
Drop the compile-time JavaScript rewriter that turned
useContext(StateContexts.x) into a shared-store read. Reflex now
generates useStateContext(...) for every state read on each compile, so
the rewriter only served hand-written hooks. It missed dynamic
components and asset JS, where legacy reads silently became null.

Remove the StateContexts export and getStateContext as well, so any
remaining legacy read fails the build with a missing-export error
instead of reading null. Add a breaking news fragment with the
migration to useStateContext.
@FarhanAliRaza

FarhanAliRaza commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

I pushed 6f0117c. It removes the legacy StateContexts rewriter.

Why:

  • Reflex now generates useStateContext(...) for every state read on each compile. The rewriter only served hooks that users type by hand.
  • StateContexts is internal. It is not documented and not public API, so it needs no compatibility layer.
  • The rewriter missed two paths. A legacy read in a dynamic component and a legacy read in an asset .jsx file both read null with no error.
  • It added a 300-line JavaScript lexer to every compile.

What changed:

  • Removed _rewrite_legacy_state_context_reads, _mask_javascript_literals_and_comments, and their tests.
  • Removed StateContexts, getStateContext, and useLegacyStateContext. A leftover legacy read now fails the build with "StateContexts" is not exported by "utils/context.jsx". It no longer reads null silently.

Tested: an app with 3000 extra states renders with no errors. A delta still re-renders only the components that read the changed substate. The focused unit tests, ruff, and pyright pass.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/reflex-base/news/+state-contexts-removed.breaking.md Outdated
- Raise reflex's reflex-base floor to 0.10.1.dev0: code_uses_state_contexts
  looks for useStateContext(, which only the new reflex-base generates.
- Drop the unrelated greenlet change in examples.yml.
- Compare applyDelta's result with the current state instead of
  pre-checking the delta, so a deep-equal delta no longer notifies.
- Skip syncInitialState when the store already holds that initial state.
- Serve the server render from a read-only store.
- Remove the duplicate listener set creation and use one useMemo in
  useStateContext.
- Mark the news fragments as bugfix with per-package text.
- Make the JS store test use a structural-sharing applyDelta stub,
  subscribe by state name, and cover an equal list delta.
@FarhanAliRaza

Copy link
Copy Markdown
Contributor

I pushed 1455221. It addresses the review findings on the shared state store:

  • reflex now requires reflex-base >= 0.10.1.dev0. code_uses_state_contexts looks for useStateContext(, which only the new reflex-base generates.
  • The greenlet change in examples.yml is reverted. It is not related to this PR.
  • The dispatcher now compares the result of applyDelta with the current state. applyDelta already keeps the old object for equal values, so the separate pre-check is gone. A deep-equal delta, for example an equal list, no longer calls listeners.
  • syncInitialState returns early when it gets the initial state the store already holds. On mount, it no longer walks every field of every substate.
  • The server provider uses a read-only store. It no longer builds dispatchers and listener sets on each server render.
  • registerSubstate no longer creates listener sets. subscribe creates them when needed.
  • useStateContext uses one useMemo instead of two useCallback calls.
  • The news fragments are now bugfix, with separate text for reflex and reflex-base.
  • The JS store test uses an applyDelta stub that shares structure like the real one. It subscribes by state name, not by array index, and it checks that an equal list does not notify.

Tested: in a test app, the render count of each component is the same as on the previous head. Hot reload still adds new states and fields. The focused unit tests, ruff, pyright, and check_min_deps for reflex pass.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread tests/units/compiler/test_context_js_template.py Outdated

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i really like this idea. it reduces the number of contexts and useState calls overall and it opens the door eventually for per-var subscriptions.

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
Attach the test listeners through the subscribe callback the hook passes
to useSyncExternalStore, so the test fails if the hook subscribes to the
wrong substate.
createStateStore takes the initial state and registers substates through
one addSubstates path, which hot reload calls for the substates of new
state classes. Fields added to existing substates are not merged here:
they arrive with the hydrate delta after the backend restarts.
Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py Outdated
mergeInitialState adds new substates and gives existing substates the
fields of the recompiled initial state at their defaults, keeping live
values. A substate is replaced and notified only when it gains a field,
so the mount-time call changes nothing.
@FarhanAliRaza
FarhanAliRaza merged commit 81f463b into reflex-dev:main Oct 15, 2026
146 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working on deck PRs lined up to review / merge next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Large State trees overflow React mutation traversal and render a blank page

3 participants