ENG-12682 Let React Compiler optimize generated components

Closed
FA
21d ago
+517-19

Stacked on #7234.

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.

Changes

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 elements (33 vs 280 cache slots on the test dashboard)moduleTypeProvider describing jsx like JSX syntax: frozen arguments, frozen result

The experimental compiler (0.0.0-experimental-a1856f3-20260507) is required. 1.0.0 still rejects refs passed to jsx() and optional calls in ternary tests.

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

Measurements

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

Compiler page coverage with auto_memoize=False: 17/17 stateful pages in a feature fixture, plus 7/8 in a built-in component sweep. The sweep covers Radix, markdown, code, data editor, gridjs, Recharts, Plotly, moment, and event patterns. rx.auto_scroll still bails because its hook code mutates a ref value in render-scope helpers. The app-root Toaster still writes refs['__toast'] during render, and it renders once.

Python compile time with auto-memo off:

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

Validation

  • Unit suite: 9,731 passed, 19 skipped, 79% coverage. ruff, pyright, and pre-commit pass.
  • Browser checks on both new variants passed with no page errors: rx.set_focus, controlled input round trip, and backend events. The rendered DOM is identical between compiler variants.

Known limits

  • 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.
image