Skip to content

ENG-10089: Support custom JS wrappers and bare functions in @rx.memo#6730

Open
masenf wants to merge 3 commits into
mainfrom
claude/rxmemo-wrapper-customization-hu30bw
Open

ENG-10089: Support custom JS wrappers and bare functions in @rx.memo#6730
masenf wants to merge 3 commits into
mainfrom
claude/rxmemo-wrapper-customization-hu30bw

Conversation

@masenf

@masenf masenf commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Description

Extends @rx.memo to support customizing the JavaScript wrapper applied to compiled component memos, or omitting the wrapper entirely to export bare function components.

Changes

Core feature:

  • Added DEFAULT_MEMO_WRAPPER constant: React's memo function with its own import
  • Extended @rx.memo decorator to accept optional wrapper= keyword argument:
    • @rx.memo or @rx.memo(): Uses React's memo (default behavior, unchanged)
    • @rx.memo(wrapper=custom_var): Wraps the function component in a custom JS helper (e.g., MobX observer)
    • @rx.memo(wrapper=None): Exports the bare function component with no wrapper
  • The wrapper's imports ride on the Var itself, so custom wrappers bring their own imports and wrapper=None pulls in nothing
  • wrapper= only applies to component-returning memos; var-returning memos reject the argument with a clear error

Compiler updates:

  • Modified memo_components_template and memo_single_component_template to render the wrapper conditionally
  • Updated _MEMO_BASE_IMPORTS to remove the hardcoded memo import (now supplied by DEFAULT_MEMO_WRAPPER)
  • Extended compile_experimental_component_memo to extract and merge wrapper var data into the imports collection

Documentation:

  • Added "Customizing the JavaScript Wrapper" section with examples
  • Updated API reference to document the wrapper parameter

Tests

  • Added 8 new unit tests covering:
    • Default wrapper behavior (test_default_memo_wrapper_is_react_memo, test_component_memo_default_wrapper)
    • Bare function export (test_component_memo_wrapper_none_emits_bare_function, test_component_memo_wrapper_none_in_unmirrored_module)
    • Custom wrappers (test_component_memo_custom_wrapper)
    • Error handling (test_var_returning_memo_rejects_wrapper)
    • Decorator syntax (test_memo_decorator_parens_form_matches_bare_decorator)
  • Added integration test (test_memo_wrapper_none_renders_and_updates) verifying bare function components render and update correctly

Backward compatibility

Fully backward compatible. Existing @rx.memo decorators continue to work unchanged, using React's memo by default.

https://claude.ai/code/session_01CtJ4TyjTc7nDyDyFn446TH

@rx.memo now accepts a wrapper= keyword controlling the JS function the
compiled function component is wrapped in. The default is unchanged:
React's memo, now expressed as DEFAULT_MEMO_WRAPPER — a FunctionStringVar
carrying its own react import. Passing another Var swaps the wrapper (its
VarData imports ride along, feeding module imports and frontend package
detection), and wrapper=None exports the bare function component.

- MemoComponentDefinition grows a wrapper field; the compiler emits it via
  the render dict and a shared _render_memo_component template helper.
- The react memo import moves off _MEMO_BASE_IMPORTS onto the wrapper var,
  so modules whose memos swap or drop the wrapper no longer import it.
- Var-returning memos reject an explicit wrapper= with a TypeError.
- @rx.memo() with no arguments now also works, typed via a _MemoDecorator
  protocol so both component- and var-returning memos check cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtJ4TyjTc7nDyDyFn446TH
@masenf masenf requested review from a team and Alek99 as code owners July 9, 2026 22:56
@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds configurable JavaScript wrappers for @rx.memo components. The main changes are:

  • A default React memo wrapper carried by wrapper metadata.
  • Support for custom wrapper vars and wrapper=None bare function components.
  • Compiler and template updates for wrapper rendering and imports.
  • Docs, unit tests, and integration coverage for the new wrapper modes.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/components/memo.py Adds the wrapper API, default wrapper sentinel, and component-only wrapper validation.
packages/reflex-base/src/reflex_base/compiler/templates.py Renders memo component exports with an optional wrapper expression.
reflex/compiler/utils.py Adds wrapper import collection and passes wrapper expressions into memo rendering.
reflex/compiler/compiler.py Moves the React memo import out of shared base imports.
tests/units/components/test_memo.py Adds coverage for default, custom, omitted, and rejected wrapper cases.
tests/integration/tests_playwright/test_memo.py Adds browser coverage for a bare memo component rendering and updating.
docs/library/other/memo.md Documents custom wrapper usage and the component-only restriction.

Reviews (3): Last reviewed commit: "Add changelog news fragments for memo wr..." | Re-trigger Greptile

Comment thread packages/reflex-base/src/reflex_base/components/memo.py
@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/rxmemo-wrapper-customization-hu30bw (947ca6c) with main (948c619)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (3b97060) during the generation of this report, so 948c619 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0bb48818b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/reflex-base/src/reflex_base/compiler/templates.py
claude added 2 commits July 10, 2026 00:17
An inline wrapper expression like `(Comp) => observer(Comp)` concatenated
directly against the component function would swallow the call into the
arrow body instead of invoking the wrapper. Wrap anything that isn't an
identifier/member chain in parens; `memo(...)` output is unchanged.

Addresses PR #6730 review feedback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtJ4TyjTc7nDyDyFn446TH
@masenf masenf changed the title Support custom JS wrappers and bare functions in @rx.memo ENG-10089: Support custom JS wrappers and bare functions in @rx.memo Jul 10, 2026
@linear-code

linear-code Bot commented Jul 10, 2026

Copy link
Copy Markdown

ENG-10089

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants