ENG-12682 Let React Compiler optimize generated components

FA
2d ago
+558-21

Supersedes #7272. That PR was stacked on #7234 and auto-closed, unmerged, when its base branch was deleted after #7234 merged. This PR replays its changes onto current main: the diff between the base-branch head it last merged (07aa53cb6) and its head (4f3e5dbba). Farhan is the commit author.

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

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update (included: docs/advanced_onboarding/configuration.md)

Changes

React Compiler bailed out on most generated pages and memo wrappers because of patterns Reflex emits. This PR changes those patterns. It also adds an auto_memoize option, so an app using react_compiler can let the compiler memoize whole pages instead of Reflex's generated memo wrappers.

BailoutFix
refs["ref_x"] = ref_x during render ("This value cannot be modified")const ref_x = useRegisteredRef("ref_x"). The hook lives in utils/state.js, outside compiler scope, and keeps the same registry behavior.
useMemo(generateUUID, []) ("Expected the first argument to be an inline function expression")useMemo(() => generateUUID(), [])
useCallback deps list addEvents, ReflexEvent, and unused hook bindings such as setFilesById ("Found extra memoization dependencies")Only bindings the callback references
Capture-free (...args) => addEvents(...) handlers crash function outlining ("Expected temporaries to be promoted")enableFunctionOutlining: false
jsx(type, props, ...children) from Emotion is an unknown function, so compiled pages cannot cache elementsmoduleTypeProvider describing jsx like JSX syntax: frozen arguments, frozen result

The experimental compiler (0.0.0-experimental-a1856f3-20260507) is required, because 1.0.0 still rejects refs passed to jsx() and optional calls in ternary tests. This is still npm's current experimental tag, and REACT_COMPILER_DEV_DEPENDENCIES is the only pin; @babel/core stays at main's 7.29.7.

auto_memoize=False removes MemoizeStatefulPlugin from page compilation. App-root memoization (_memoize_app_root) is unchanged.

Conflicts with main and how they were resolved

The patch applied cleanly everywhere except two hunks:

  • component.py _get_ref_hook: #7586 removed hook positions, so main's ref hook no longer passes VarData(position=HookPosition.INTERNAL). The resolution keeps useRegisteredRef and drops the position.
  • state.js: the PR's hunk carried let backend_state_mismatch = false; as context, but #7435 removed that from main. The resolution adds only useRegisteredRef.

I also checked the memo-compiler changes on main that the patch didn't textually touch: mirrored memo modules, auto-memo keying in memoize.py, and compiler.py. default_page_plugins is still the only place page compilation adds MemoizeStatefulPlugin, so the auto_memoize switch still covers every page path.

Measurements (from the original PR #7272)

Production builds, Chromium, direct state dispatch, 11 alternating rounds × 100 updates. Dashboard page: sidebar, 8 KPI cards, two Recharts charts, tabs with a form and a 10-item list, static cards.

UpdateTodayreact_compilerreact_compiler + auto_memoize=False
One KPI value0.87 ms, 171 renders0.095 ms, 28 renders0.106 ms, 10 renders
Search input0.99 ms0.144 ms0.163 ms
Chart data5.90 ms5.19 ms5.06 ms
Activity list0.89 ms0.33 ms0.42 ms
Header select1.06 ms0.27 ms0.30 ms

Python compile time with auto-memo off (from #7272):

AppAuto-memo onAuto-memo off
Dashboard fixture200 ms128 ms
Feature fixture179 ms144 ms
Docs site42.2 s31.7 s

Testing

  • uv run ruff check ., uv run ruff format ., uv run pyright reflex tests (0 errors), and uv run pre-commit run --all-files all pass. The .pyi hook made no changes.
  • tests/units/compiler, tests/units/reflex_base, test_prerequisites.py, test_config.py, and components/test_component.py: 1943 passed.
  • Full unit suite: 15274 passed. The only failures were the 14 reflex_bench/drivers/test_browser.py tests, because the dev container lacks the Playwright headless-shell binary those tests need. That is unrelated to this diff.
  • Smoke app with react_compiler=True, auto_memoize=False under reflex run --env prod:
    • It compiles and builds, with no memo wrappers generated.
    • The page chunk is compiler-transformed (memo cache sentinels are present).
    • In Chromium, three backend increments, rx.set_focus through useRegisteredRef, and a controlled-input round trip all worked, with no page errors.

Known limits (from #7272)

  • With auto_memoize=False, a page the compiler skips re-renders fully on every state change, about 2× slower than today. The docs say to measure before turning it off.
  • Custom components that inject raw hook JavaScript can still cause bailouts.

https://claude.ai/code/session_01LwJUCTv3FVWzi1UoUpyMLT


Generated by Claude Code