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
8 changes: 8 additions & 0 deletions reflex/experimental/memo.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,14 @@ def passthrough(children: Var[Component]) -> Component:
new_component = copy(component)
if render_snapshot:
return new_component
# Components with no original structural children own their own JSX
# output (e.g. ``CodeBlock`` injects ``code`` as the ``children`` prop
# in ``_render``). Substituting a ``{children}`` hole here would emit
# ``jsx(Inner, {children: "..."}, hole)``, and an undefined hole at
# call time clobbers the prop. Skip the substitution so the wrapper's
# ``children`` parameter is present in the signature but unused.
if not component.children:
return new_component
Comment thread
carlosabadia marked this conversation as resolved.
hole_bare = Bare.create(children)
captured_hole_child.append(hole_bare)
# Substitute the ``{children}`` hole for the original descendants so
Expand Down
29 changes: 29 additions & 0 deletions tests/units/compiler/test_memoize_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ class LeafComponent(Component):
_memoization_mode = MemoizationMode(recursive=False)


class ChildrenViaProp(Component):
"""Stub mirroring ``CodeBlock`` — injects its content as ``children`` prop."""

tag = "ChildrenViaProp"
library = "children-via-prop-lib"

code: Var[str] = component_field(default=LiteralVar.create(""))

def _render(self):
return super()._render().remove_props("code").add_props(children=self.code)


class SpecialFormMemoState(BaseState):
items: Field[list[str]] = field(default_factory=lambda: ["a"])
flag: Field[bool] = field(default=True)
Expand Down Expand Up @@ -417,6 +429,23 @@ def test_generated_memo_component_is_not_itself_memoized() -> None:
assert not _should_memoize(wrapper)


def test_passthrough_memo_skips_hole_for_childless_component() -> None:
"""Childless components own their JSX output, so the wrapper must not
inject a ``{children}`` hole.

Regression: components like ``CodeBlock`` set ``children`` on their own
rendered Tag via ``_render``. Substituting a ``Bare({children})`` hole
would emit ``jsx(Inner, {children: "..."}, hole)``, and at call time the
undefined hole arg overwrites ``props.children`` under Emotion's jsx
semantics — causing every reactive ``rx.code_block`` to render an empty
``<code>`` element.
"""
component = ChildrenViaProp.create(code=STATE_VAR)
assert not component.children
_wrapper_factory, definition = create_passthrough_component_memo(component)
assert definition.passthrough_hole_child is None


def test_event_trigger_memoization_not_emit_usecallback_in_page_hooks() -> None:
"""Components with event triggers do not get useCallback wrappers at the page level."""
from reflex_base.event import EventChain
Expand Down
Loading