feat: add experimental memo decorator for JS-level component and function memoization#6192
feat: add experimental memo decorator for JS-level component and function memoization#6192FarhanAliRaza wants to merge 13 commits intoreflex-dev:mainfrom
Conversation
…tion memoization Introduce rx.experimental.memo (rx._x.memo) that allows memoizing components and plain functions at the JavaScript level. Supports component memos with typed props (including children and rest props via RestProp), and function memos that emit raw JS. Updates the compiler pipeline to handle both memo kinds alongside existing CustomComponent memos, and refactors signature rendering to use DestructuredArg.
Greptile SummaryThis PR introduces Key changes:
Issues found:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User as User Code
participant Memo as @rx._x.memo
participant Registry as EXPERIMENTAL_MEMOS
participant Compiler as compiler.py
participant Utils as compiler/utils.py
participant Template as templates.py
User->>Memo: @rx._x.memo on fn (→ Component or → Var)
Memo->>Memo: _analyze_params(fn)
Memo->>Memo: _evaluate_memo_function(fn, params)
alt Component-returning
Memo->>Memo: _create_component_definition()
Memo->>Memo: _lift_rest_props(component)
Memo->>Registry: _register_memo_definition(ExperimentalMemoComponentDefinition)
Memo-->>User: _create_component_wrapper() → callable
else Var-returning
Memo->>Memo: _create_function_definition()
Memo->>Memo: _validate_var_return_expr()
Memo->>Registry: _register_memo_definition(ExperimentalMemoFunctionDefinition)
Memo-->>User: _create_function_wrapper() → callable
end
User->>User: App.compile()
User->>Compiler: compile_memo_components(CUSTOM_COMPONENTS, EXPERIMENTAL_MEMOS)
loop each ExperimentalMemoComponentDefinition
Compiler->>Utils: compile_experimental_component_memo(definition)
Utils->>Utils: copy.deepcopy(definition.component)
Utils->>Utils: _apply_component_style_for_compile()
Utils-->>Compiler: (render_dict, imports)
end
loop each ExperimentalMemoFunctionDefinition
Compiler->>Utils: compile_experimental_function_memo(definition)
Utils-->>Compiler: (function_dict, imports)
end
Compiler->>Template: memo_components_template(components, functions, ...)
Template-->>Compiler: JS file with export const ... = memo(...) and export const ... = ((...) => ...)
|
… components Add registry helpers that detect duplicate exported names across memo kinds and raise on collision. Deepcopy the component before applying styles during compilation so the stored definition stays clean. Simplify the function wrappers .call to alias the wrapper itself.
| monkeypatch.setenv( | ||
| constants.PYTEST_CURRENT_TEST, | ||
| "tests/integration/test_experimental_memo.py::test_experimental_memo_app", | ||
| ) | ||
| monkeypatch.setattr(reflex_app, "is_testing_env", lambda: True) | ||
| monkeypatch.setattr(reflex_state, "is_testing_env", lambda: True) |
masenf
left a comment
There was a problem hiding this comment.
some initial feedback.
please mark any greptile comments you have addressed as resolved, some of them seem out of date with the latest diff
reflex/experimental/memo.py
Outdated
| """ | ||
| params = _analyze_params(fn, for_component=True) | ||
| component = _evaluate_memo_function(fn, params) | ||
| if not isinstance(component, Component): |
There was a problem hiding this comment.
there is a valid case where we would have a ComponentVar here, for example if the memo function returns an rx.cond construct
There was a problem hiding this comment.
Updated it .
masenf
left a comment
There was a problem hiding this comment.
i was testing this out and so far it's working really well and is awesome to be able to have the children and rest prop accessible inside the memo function.
one strange thing i noticed is that CSS props don't seem to be handled through the RestProp.
for example, if i pass color="rebeccapurple" to the wrapper function, it ends up getting passed straight through and shows up in the DOM instead of being processed at some earlier stage and converted into the css prop that emotion recognizes.
if i pass the prop as a style dict, style={...}, then it gets processed correctly.
i'm not sure it's possible to handle this in the general case, because we don't know which of the props are considered style props and which are functional props to the underlying component. this might just have to be a documentation thing, unless you can think of a way to handle it.
…memo internals Convert remaining_props keys to camelCase in _bind_function_runtime_args so rest props (e.g. class_name → className) match the component memo behavior. Also make MemoParam kw_only, return a tuple from get_props instead of a dict, and remove unnecessary monkeypatch boilerplate from the integration test fixture.
Replace _create_function_wrapper and _create_component_wrapper closures with _ExperimentalMemoFunctionWrapper and _ExperimentalMemoComponentWrapper classes, eliminating object.__setattr__ hacks for call/partial/_as_var in favor of real methods.
Extract _normalize_component_return to wrap Var[Component] values in Bare.create, allowing memos that return rx.cond or other component-typed vars to be registered as component memos. Add a cond overload for (Any, Var[Component], Var[Component]) -> Component.
…level Replace instance-level self.tag assignment with cached dynamically created ExperimentalMemoComponent subclasses via _get_experimental_memo_component_class, so the tag is a class-level attribute rather than set in _post_init.
Introduce rx.experimental.memo (rx._x.memo) that allows memoizing components and plain functions at the JavaScript level. Supports component memos with typed props (including children and rest props via RestProp), and function memos that emit raw JS. Updates the compiler pipeline to handle both memo kinds alongside existing CustomComponent memos, and refactors signature rendering to use DestructuredArg.
All Submissions:
Type of change
Please delete options that are not relevant.
New Feature Submission:
Changes To Core Features:
closes #6186