Repository navigation
Create separate context providing components for each state - #6181
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
|
Instead of combining all states and dispatchers into the same component (which is the re-render domain), separate each state into its own provider component and combine those into StateProvider. Since StateProvider doesn't directly depend on each state's context, it doesn't have to re-render just because some substate changed. - `StateProvider` keeps its exported name -- it is the app-wrap tag in `reflex_base.components.state_context` -- and is now a pure composer; the per-substate reducer lives in a new `SubstateProvider`. - `DispatchProvider` publishes the plain registry object (`ref.current`) rather than the ref wrapper, so reads and writes agree. - Dispatcher registration runs in a layout effect. `EventLoopProvider` mounts below the state providers, so its passive effect connects the socket before any ancestor passive effect would have registered a dispatcher, and `state.js` treats a delta for an unregistered substate as a fatal state mismatch. Falls back to `useEffect` on the server, where `useLayoutEffect` warns. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015aV7hXqYysQQCFAu7dKxNX
844077c to
7473030
Compare
# Conflicts: # packages/reflex-base/src/reflex_base/compiler/templates.py # tests/units/compiler/test_compiler.py
|
The final CodSpeed check flagged |
FarhanAliRaza
left a comment
There was a problem hiding this comment.
I tested this PR in a real app against a main baseline. The two builds differ only in templates.py.
The app has a root state, sibling substates, a nested substate, a computed var, an event that changes two substates through get_state, a generator event, and two pages with on_load. I added 300 generated substates to make the provider cost visible. A React commit hook counted the components that rendered in each commit and recorded the commit duration.
On main, each delta renders StateProvider and the one consumer. With this PR, each delta renders one SubstateProvider and the one consumer. Consumer renders are the same on both builds. With 310 substates, the median commit duration goes from 1.1 ms to 0.2 ms over 200 clicks.
I also exercised a two-substate delta, a 50-click burst, client navigation between the pages, a hard load on the second page, and a dev hot reload that adds a state var. All deltas dispatched correctly and the browser console stayed clean. A frontend export succeeds.
The requested changes are in the inline comments.
There was a problem hiding this comment.
1 issue found across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/reflex-base/news/6181.performance.md">
<violation number="1" location="packages/reflex-base/news/6181.performance.md:1">
P3: The fragment names the generated file `utils/context.js`, but the compiler writes it with a JSX extension: `reflex/compiler/utils.py:871` emits `get_web_dir() / (constants.Dirs.CONTEXTS_PATH + constants.Ext.JSX)`, where `CONTEXTS_PATH` is `"utils/context"` (packages/reflex-base/src/reflex_base/constants/base.py:40). Update the fragment to `utils/context.jsx` so the changelog entry matches the shipped file.</violation>
</file>
… verified; FINDING-023 latch re-confirmed, 024, 025) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeyxWsuC9hqSKq8YcEwZyY
…ng noise per re-verification) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeyxWsuC9hqSKq8YcEwZyY
The generated
StateProvidercurrently owns every substate reducer, so each delta re-renders that combined provider and recreates the provider elements for all substates. Give each substate its own reducer and provider so a delta only re-renders the providers it touches. Consumer updates continue to depend on their context values.Dispatchers register into a stable shared registry during the layout phase, before the event loop connects the WebSocket.
DispatchProvider's memo depends only onchildren, since the registry ref has a stable identity.Validation: 282 compiler tests, all eight router browser cases across development and production modes, and all pre-commit hooks pass. The new browser case extends the existing router app: its initial on-load event updates two sibling substates, and the test verifies both rendered values plus a single received delta containing both updates. It replaces the source-string test that did not establish runtime ordering.