Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/reflex-base/src/reflex_base/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,15 +356,20 @@ def __copy__(self) -> BaseComponent:
new = self.__class__.__new__(self.__class__)
new_dict = vars(new)
new_dict.update(vars(self))
new._clear_compile_caches()
return new

def _clear_compile_caches(self) -> None:
"""Clear cached render/compiler artifacts after compile-time mutation."""
attrs = cast("dict[str, Any]", vars(self))
for attr in (
"_cached_render_result",
"_vars_cache",
"_imports_cache",
"_hooks_internal_cache",
"_get_component_prop_property",
):
new_dict.pop(attr, None)
return new
attrs.pop(attr, None)

def __eq__(self, value: Any) -> bool:
"""Check if the component is equal to another value.
Expand Down Expand Up @@ -1367,6 +1372,7 @@ def _add_style_recursive(

# Assign the new style
self.style = new_style
self._clear_compile_caches()

# Recursively add style to the children.
for child in self.children:
Expand Down
40 changes: 40 additions & 0 deletions tests/units/compiler/test_memoize_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,46 @@ def special_child() -> Component:
assert body_marker not in page_output


def test_foreach_snapshot_memo_applies_component_styles() -> None:
"""Foreach memo bodies must render styled children after preview rendering.

Regression for reflex-dev/reflex#6512: the auto-memo wrapper previews a
Foreach render before the memo body is compiled. If that unstyled preview
render stays cached, default styles from components inside the Foreach
never reach the generated memo module.
"""
from reflex.compiler.compiler import compile_memo_components

def accordion() -> Component:
return rx.accordion.root(
rx.accordion.item(
header="Click me",
content=rx.text("Content here"),
),
type="single",
collapsible=True,
width="400px",
)

ctx, _page_ctx = _compile_single_page(
lambda: rx.vstack(
rx.foreach(SpecialFormMemoState.items, lambda _item: accordion()),
)
)

memo_files, _memo_imports = compile_memo_components(
components=(),
experimental_memos=tuple(ctx.auto_memo_components.values()),
)
foreach_code = next(
code for path, code in memo_files if "/Foreach" in path or "\\Foreach" in path
)

assert '["width"] : "400px"' in foreach_code
assert '["borderRadius"] : "var(--radius-4)"' in foreach_code
assert '["justifyContent"] : "space-between"' in foreach_code


def test_foreach_parent_does_not_absorb_sibling_into_snapshot() -> None:
"""Foreach owns its own snapshot while the parent stays passthrough.

Expand Down
Loading