Skip to content

Commit

Permalink
Make @rx.memo work with state vars passed as props (#2810)
Browse files Browse the repository at this point in the history
* Make @rx.memo work with state vars passed as props

Seems like this was a regression from the StatefulComponent refactor, because
trying to pass a state Var to a CustomComponent gave undefined, likely due to
`_get_vars` not accounting for `self.props` in CustomComponents.

With this change, it works.

Integration test added to `test_var_operations.py`

* Allow CustomComponent props to be Component

Avoid calling `.json()` on all Base types because the Var serializer already
does that, but this way, more specific types (like Component) can be serialized
differently.

When the type is Component, attach a VarData with the imports and hooks to when
the Var is rendered, it also carries the correct imports/hooks and does not
throw frontend errors.
  • Loading branch information
masenf authored Mar 13, 2024
1 parent 4c10cbc commit 036afa9
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 5 deletions.
20 changes: 20 additions & 0 deletions integration/test_var_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ class VarOperationState(rx.State):

app = rx.App(state=rx.State)

@rx.memo
def memo_comp(list1: list[int], int_var1: int, id: str):
return rx.text(list1, int_var1, id=id)

@rx.memo
def memo_comp_nested(int_var2: int, id: str):
return memo_comp(list1=[3, 4], int_var1=int_var2, id=id)

@app.add_page
def index():
return rx.vstack(
Expand Down Expand Up @@ -566,6 +574,15 @@ def index():
),
id="foreach_list_nested",
),
memo_comp(
list1=VarOperationState.list1,
int_var1=VarOperationState.int_var1,
id="memo_comp",
),
memo_comp_nested(
int_var2=VarOperationState.int_var2,
id="memo_comp_nested",
),
)


Expand Down Expand Up @@ -759,6 +776,9 @@ def test_var_operations(driver, var_operations: AppHarness):
("foreach_list_arg", "1\n2"),
("foreach_list_ix", "1\n2"),
("foreach_list_nested", "1\n1\n2"),
# rx.memo component with state
("memo_comp", "1210"),
("memo_comp_nested", "345"),
]

for tag, expected in tests:
Expand Down
29 changes: 24 additions & 5 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,12 +1303,18 @@ def __init__(self, *args, **kwargs):

# Handle subclasses of Base.
if types._issubclass(type_, Base):
try:
value = BaseVar(
_var_name=value.json(), _var_type=type_, _var_is_local=True
base_value = Var.create(value)

# Track hooks and imports associated with Component instances.
if base_value is not None and types._issubclass(type_, Component):
value = base_value._replace(
merge_var_data=VarData( # type: ignore
imports=value.get_imports(),
hooks=value.get_hooks(),
)
)
except Exception:
value = Var.create(value)
else:
value = base_value
else:
value = Var.create(value, _var_is_string=type(value) is str)

Expand Down Expand Up @@ -1393,6 +1399,19 @@ def get_prop_vars(self) -> List[BaseVar]:
for name, prop in self.props.items()
]

def _get_vars(self, include_children: bool = False) -> list[Var]:
"""Walk all Vars used in this component.
Args:
include_children: Whether to include Vars from children.
Returns:
Each var referenced by the component (props, styles, event handlers).
"""
return super()._get_vars(include_children=include_children) + [
prop for prop in self.props.values() if isinstance(prop, Var)
]

@lru_cache(maxsize=None) # noqa
def get_component(self) -> Component:
"""Render the component.
Expand Down

0 comments on commit 036afa9

Please sign in to comment.