Skip to content

Render state contexts without a component per substate on the server - #7369

Merged
masenf merged 3 commits into
mainfrom
fix/server-state-provider-depth
Oct 1, 2026
Merged

masenf merged 3 commits into
mainfrom
fix/server-state-provider-depth

Conversation

@adhami3310

@adhami3310 adhami3310 commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

What and why

#6181 gave every substate its own SubstateProvider so a delta re-renders only the contexts it changes. On the server that component has no job (the page renders once and no delta is ever applied), but it adds a second render level per substate. React's server renderer recurses once per level, so the depth of every page render doubled.

In an app with ~150 substates this intermittently failed every route in the dev server with a 500 (Unexpected Server Error / SyntaxError: Invalid regular expression: /[^a-z-]/: Stack overflow) that cleared on its own after 40–60 s. Measured on that app:

  • Before V8 has optimised React's render functions (after a restart, and again after a major GC), the render reaches the first Emotion css element 1,254 frames deep with about 7 KB of the default ~984 KB stack left.
  • React Fizz recovers from overflows whose message is exactly Maximum call stack size exceeded: it re-renders that subtree from a fresh stack. An overflow that surfaces as anything else is not recovered, and the request fails. The regex SyntaxError above is one example; a TypeError reading $$typeof on a useContext is another.
  • Running the same app with the stack only 1–2.5% smaller (--stack-size=971 and 960) reproduced the 500s reliably, including the fail-then-recover pattern.

Change

context_template now emits the substates once as a SUBSTATES table, plus two providers chosen at module load:

  • ClientStateProvider (browser): unchanged behaviour. DispatchProvider and one SubstateProvider per substate, memoized on children, built with a loop instead of a generated nested expression.
  • ServerStateProvider (typeof document === "undefined", the same test useIsomorphicLayoutEffect uses): bare context providers carrying initialState, one level per substate. No reducers or dispatch registry, because nothing dispatches on the server.

Hydration is unaffected: wrapper components produce no DOM and do not fork the tree. In the app above there were no hydration warnings on any page, with the same console output as before the change.

Same app, with the fix:

Before After
Depth of first css element 1,254 frames 646 frames
Stack free on a cold render ~0.7% ~45%
--stack-size 950–990 KB and 960/971 500s at 960 and 971 all clean
--stack-size down to 500 KB not tested all clean

Tests

  • test_context_template_server_state_provider_is_flat (new) and the updated test_context_template_one_provider_per_substate fail on main and pass here.
  • test_context_js_template.py located EventLoopProvider by the old export function StateProvider text; it now anchors on the next declaration.
  • tests/units/compiler: 308 passed. The full unit suite has the same failures with and without this change (227 reflex_cli tests that fail in my environment on clean main too).
  • tests/integration/tests_playwright/test_router_query.py: 8 passed in dev and prod, including Create separate context providing components for each state #6181's case where one delta updates two sibling substates.
  • pre-commit hooks pass on the changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQ4shGzkLwvScpUv6vxqHm


Generated by Claude Code

Review in cubic

The server renders the page once and never applies a delta, so the
per-substate SubstateProvider (#6181) only adds a render level per substate
there. The server renderer recurses once per level, and with ~150 substates
a cold render used nearly the whole Node stack, intermittently failing every
route with a non-RangeError overflow that React cannot recover from.

On the server, StateProvider now provides the initial state through bare
context providers; the browser keeps one SubstateProvider per substate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQ4shGzkLwvScpUv6vxqHm
@adhami3310
adhami3310 requested a review from a team as a code owner September 30, 2026 23:13
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[High risk] Refactors how state contexts render on the server.

The PR appears safe to merge; no outstanding finding or new actionable issue remains.

Summary

The PR reduces server-render depth by emitting bare initial-state context providers on the server while retaining per-substate providers in the browser.

  • Adds a Node-based regression test that checks the provider tree for 200 substates.
  • Updates the changelog to describe the user-facing fix.

Reviews (3) · Last reviewed commit: "Render the server state provider in a te..."

@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.

No issues found across 4 files

You’re at about 92% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Re-trigger cubic

Comment thread packages/reflex-base/news/7369.bugfix.md Outdated
Comment thread tests/units/compiler/test_compiler.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQ4shGzkLwvScpUv6vxqHm
@codspeed

codspeed Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 4.21%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 146 untouched benchmarks
⏩ 17 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ test_var_access[non_mutable_scalar] 33.1 ms 34.6 ms -4.21%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/server-state-provider-depth (a419581) with main (02efed7)

Open in CodSpeed

Footnotes

  1. 17 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

The existing test only checks the generated text. This one imports the
generated context module in node, with the modules it imports stubbed,
and walks the tree StateProvider returns on the server for 200 substates:
one context per substate, each with its initial state, and no component
or hook between them. It fails against the template before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQ4shGzkLwvScpUv6vxqHm

adhami3310 commented Sep 30, 2026 •

Copy link
Copy Markdown
Member Author

CodSpeed Performance Analysis reports a 4.2% regression in tests/benchmarks/test_state_proxy.py::test_var_access[non_mutable_scalar] (33.1 ms → 34.6 ms) on a419581. I don't think this PR causes it:

  • a419581 changes only a unit test file and the changelog fragment. The library code is identical to ce103f7, which CodSpeed measured as "will not alter performance" across the same 147 benchmarks.
  • This PR only changes the generated context.js, a frontend file. That benchmark measures Python state-proxy var access and never touches it.
  • CodSpeed also warns that this comparison mixes runtime environments.

I tried to re-run the benchmark workflow (run 36791240447) to confirm, but I don't have permission (403). A maintainer can re-run it, or acknowledge the result in CodSpeed.


Generated by Claude Code

@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.

Cool fix

@masenf
masenf merged commit dcc38cb into main Oct 1, 2026
144 of 145 checks passed
@masenf
masenf deleted the fix/server-state-provider-depth branch October 1, 2026 00:07
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.

3 participants