Auto-memoize user @rx.memo components with stateful props - #6949
Conversation
`MemoComponent` opted out of auto-memoization wholesale via `MemoizationDisposition.NEVER`, so binding a state Var (or an event handler) to a prop at the call site left the state `useContext` in the page module. React's `memo` then only spared the memoized component's own subtree: every state change still re-rendered the page and every static sibling in it. Move the opt-out from `MemoComponent` to just the wrappers the auto-memoize pass generates (they already are the boundary, and wrapping them would recurse), tracked by a new `auto_memo_wrapper` flag on `MemoComponentDefinition` that `_get_memo_component_class` turns into the `NEVER` disposition. User-authored memos now run through the normal heuristic: static props stay inline, while state-bound props and event handlers get a generated wrapper that holds the hooks. The page then carries no dependency on the state, the wrapper absorbs the re-render, and React's `memo` stops there unless a prop value actually changed — so a call site can punch a single Var dependency through to an expensive component. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FNfPvVUGGMUQHh63V4Phbe
Greptile SummaryThe PR extends compiler auto-memoization to isolate stateful props and event handlers passed to user-authored
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/reflex-base/src/reflex_base/components/memo.py | Adds explicit generated-wrapper metadata and conditionally applies the recursion-prevention memoization disposition. |
| reflex/compiler/plugins/memoize.py | Allows user-authored memo components to pass through the existing stateful auto-memoization heuristic. |
| tests/units/compiler/test_memoize_plugin.py | Covers wrapper opt-out, stateful and static props, event callbacks, child passthrough, and foreach ownership. |
| tests/integration/tests_playwright/test_memo.py | Verifies that stateful memo props and children update correctly in the browser. |
| docs/library/other/memo.md | Documents how generated wrappers isolate call-site state dependencies. |
Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Add news fragments for the reflex and reflex-base packages, and correct the docs paragraph: the page function no longer re-runs, but a sibling reading the same state still re-renders inside its own generated wrapper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FNfPvVUGGMUQHh63V4Phbe
…tateful-props-dqga0l # Conflicts: # packages/reflex-base/src/reflex_base/components/memo.py # pyi_hashes.json
Type of change
Description
Extends the compiler's auto-memoization pass to wrap user-authored
@rx.memocomponents when they're called with state-bound props or event handlers. This ensures state hooks compile into a generated wrapper instead of the page module, preventing unnecessary full-page re-renders on state changes.Key changes:
Auto-memo wrapper generation (
reflex/compiler/plugins/memoize.py):_should_memoize()to treat@rx.memocomponents as eligible for wrapping when bound to stateMemoizationDisposition.NEVERMemo component definition (
packages/reflex-base/src/reflex_base/components/memo.py):auto_memo_wrapperflag toMemoComponentDefinitionto distinguish compiler-generated wrappers from user code_get_memo_component_class()to applyNEVERdisposition only to auto-generated wrappers, allowing user@rx.memocomponents to be wrapped when needed@rx.memoand auto-memoizationPassthrough wrapper creation (
packages/reflex-base/src/reflex_base/components/memo.py):auto_memo_wrapper=Truewhen creating passthrough wrappers viacreate_passthrough_component_memo()Documentation (
docs/library/other/memo.md):Behavior
Before: A
@rx.memocomponent called with a state Var would pull that state hook into the page module, causing the entire page to re-render on state changes.After: The compiler detects this pattern and generates a wrapper that holds the state hooks. The page renders the wrapper, which re-renders on state changes and passes the prop to the memoized component. React's
memothen skips the component unless the prop value actually changed.Testing
useCallbackin wrapperforeachrenders in foreach bodytest_memo_stateful_prop_and_children_update) verifying the feature works end-to-end with both stateful props and childrenAll tests pass with adequate coverage.
Checklist
uv run ruff check .anduv run ruff format .cleanuv run pyright reflex testspassespyi_hashes.jsonupdatedhttps://claude.ai/code/session_01FNfPvVUGGMUQHh63V4Phbe