Avatar for the reflex-dev user
reflex-dev
reflex
BlogDocsChangelog

Performance History

Latest Results

ENG-12682 Let React Compiler optimize generated components (#7694) * Let React Compiler optimize generated components Replays #7272 onto main. That PR auto-closed when its base branch was deleted after #7234 merged; its own changes were the diff between the base-branch head it last merged (07aa53cb6) and its head (4f3e5dbba), covering commits c01f4d48d, 755ddbafe and 4f3e5dbba. Change the patterns Reflex emits that made React Compiler bail out: register refs through a useRegisteredRef hook instead of writing to refs during render, wrap generateUUID in an inline useMemo callback, list only referenced bindings as useCallback dependencies, disable function outlining, and describe Emotion's jsx() to the compiler as JSX. Pin the experimental compiler, which accepts refs passed to jsx(). Add an auto_memoize config option (default True). False removes MemoizeStatefulPlugin from page compilation so the compiler memoizes whole pages; app-root memoization is unchanged. Conflicts with main: #7586 removed hook positions, so the ref hook no longer passes VarData(position=INTERNAL); #7435 removed backend_state_mismatch from state.js, so it stays removed. Claude-Session: https://claude.ai/code/session_01LwJUCTv3FVWzi1UoUpyMLT * Tie the callback prune pattern to Python names, tidy tests and fragment Export GENERATED_EVENT_CALLBACK from the React Compiler adapter and test it against the names get_memoized_event_triggers produces, so a rename on either side fails a test instead of silently restoring compiler bailouts. Drop the Unicode stale-callback claim from the reflex-base fragment: main never had that bug. Move inline test imports to module level. * Run the auto_memoize compile_app test as a dry run Without dry_run, REFLEX_SKIP_COMPILE or a leftover .web/nocompile file made compile_app return before building the page plugins, and the test wrote into the working directory's .web. * Derive the client counter test's JS from the var's real identifier (#7741) test_memo_client_counter hard-coded the JavaScript name a client state var binds its value to ("Δcounter + 1"). Merging main brought in #6936, which drops the name of a legacy ClientStateVar created with global_ref=False and suffixes the emitted identifier with RxClientState, so the hook now reads `const [cs$0RxClientState, setCs$0] = ...` while the click handler still read `Δcounter`. That bare identifier resolved to the button carrying id="Δcounter" through the browser's named element access, and the counter rendered "[object HTMLButtonElement]1". Build the increment expression from str(counter.value) instead, with a {value} placeholder for the direct read and a {source} placeholder for the eval'd reads, so the test follows whatever identifier Reflex emits. Use rx.client_state(0) and .set(...) in place of the deprecated ClientStateVar.create / set_value entry points. rx.client_state rejects non-ASCII names, so the Unicode case becomes a plain scoped counter; test_react_compiler_callback_dependencies still covers Unicode bindings in the Babel dependency prune. Claude-Session: https://claude.ai/code/session_01ESN8Dfnc4fUrUVcPsqaLGk Co-authored-by: Claude <noreply@anthropic.com> * Keep lifecycle events in their branch when auto_memoize is off Without memo wrappers, on_mount and on_unmount hooks were hoisted to the page function. A hidden rx.cond branch fired on_mount on page load, and an rx.foreach item hook read the loop variable outside the loop. Render the events as a MountEffect element beside the component instead. * Warn once that react_compiler is experimental * Render lifecycle events as elements in every compile mode An on_mount inside rx.foreach was hoisted to the Foreach memo body, where the loop variable does not exist, so the page crashed. Run InlineLifecyclePlugin with auto-memoization on as well, and rewrite a snapshot subtree before the memoize plugin seals it. * Limit the lifecycle element plugin to the experimental options Default apps keep the generated code they have on main. The plugin runs with react_compiler=True or auto_memoize=False. * Keep scope wrappers when auto_memoize is off; remove MountEffect The MountEffect element kept one instance across an rx.cond branch switch, so no lifecycle event fired, and its fragment hid the child from an as_child parent. With auto_memoize=False, still wrap a component that has a lifecycle event or hooks of its own in a memo component. A wrapper has its own identity and forwards props, and it keeps the hooks in their rx.cond branch. * Test rx.foreach item lifecycle with auto_memoize off; complete the docstring - An item's lifecycle hook compiles into a wrapper inside the loop body and receives the loop variable when auto_memoize is off. - default_page_plugins names the snapshot boundary and ALWAYS exceptions. Claude-Session: https://claude.ai/code/session_01LwJUCVweoEhh1JsZA7emsa * Keep lifecycle of prop components in a wrapper when auto_memoize is off With auto_memoize off, a lifecycle event of a component held in a prop moved into the page function, so it fired on page load even inside a hidden rx.cond branch. The scope-only rule now uses _has_instance_hooks, which also checks components held in props. Claude-Session: https://claude.ai/code/session_01LwJUCVweoEhh1JsZA7emsa --------- Co-authored-by: Masen Furer <m_github@0x26.net> Co-authored-by: Claude <noreply@anthropic.com>
main
2 hours ago
Lint and type-check the frontend package, and pin what it exports The package had tests but nothing that reads the code the way a reviewer does. Three gates now run in CI beside them (`npm run check`): - biome lint, configured in lint.biome.jsonc. Its findings in the source are fixed: strict equality throughout, `Array.isArray`, a radix for `parseInt`, `Number.isNaN` in place of self-comparison, a scoped `case` declaration, and an `Object.assign` where a reduce copied its accumulator on every event. None of it changes behaviour. - tsc over the JSDoc (`checkJs`), with nothing emitted, so there is still no compile step between the package and an app. types/globals.d.ts declares what the runtime reads that no module defines: the hooks other code puts on `window`, the per-app env.json, Vite's `import.meta.hot`. An undeclared global now fails the check. - tests/public_api.test.js lists the names each module exports, since compiled pages and separately released component packages import them by name, and imports every module in node, where a production build prerenders pages. state.js evaluates event code with `eval`, which resolves names from the scopes around it. The linter's unused-variable rules are off for that file for this reason, with the reason written next to the setting, and the README gains a section on this and the rest of the state that does not appear in function signatures. The config is not named biome.jsonc: the repository's format hook runs biome from the root without a configuration and stops when it finds a nested one. Claude-Session: https://claude.ai/code/session_01LwJUCSZCLWw5meAowrx3Qt
claude/js-package-extraction-testing-obmwr4
3 hours ago

Latest Branches

CodSpeed Performance Gauge
-5%
Move the frontend javascript into its own package, reflex-frontend#7794
3 hours ago
fec30c8
claude/js-package-extraction-testing-obmwr4
CodSpeed Performance Gauge
-5%
ENG-12682 Let React Compiler optimize generated components#7694
3 hours ago
da63fcc
farhan/react-compiler-generation-revive
CodSpeed Performance Gauge
-5%
4 hours ago
cf06bc1
farhan/foreach-scope-vars
© 2026 CodSpeed Technology
Home Terms Privacy Docs