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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make `rx.moment` a `MemoizationLeaf` so a stateful date child is not memo-wrapped, which react-moment parsed like `moment({})` (today at midnight).
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import dataclasses
import datetime

from reflex_base.components.component import NoSSRComponent, field
from reflex_base.components.component import MemoizationLeaf, NoSSRComponent, field
from reflex_base.event import EventHandler, passthrough_event_spec
from reflex_base.utils.imports import ImportDict
from reflex_base.vars.base import LiteralVar, Var
Expand All @@ -26,7 +26,7 @@ class MomentDelta:
milliseconds: int | None = dataclasses.field(default=None)


class Moment(NoSSRComponent):
class Moment(NoSSRComponent, MemoizationLeaf):
"""The Moment component."""

tag: str | None = "Moment"
Expand Down
2 changes: 1 addition & 1 deletion pyi_hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"packages/reflex-components-gridjs/src/reflex_components_gridjs/datatable.pyi": "2ce1c076ecf5c2fa4945b4abdbf2f91d",
"packages/reflex-components-lucide/src/reflex_components_lucide/icon.pyi": "2e1da186a37e2bb8a1d90e16ee9a63b5",
"packages/reflex-components-markdown/src/reflex_components_markdown/markdown.pyi": "79d0a59b1ba12a2f2c4a09fa6b5c776f",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "eabf233471bc5b94084914f6f35ecd66",
"packages/reflex-components-moment/src/reflex_components_moment/moment.pyi": "85d515f5254bb4c188075873ec4d8a51",
"packages/reflex-components-plotly/src/reflex_components_plotly/plotly.pyi": "80b36863336e53c050cc61386f1c9271",
"packages/reflex-components-radix/src/reflex_components_radix/__init__.pyi": "a77352f60fb6f4135b5d08a6e56efa6d",
"packages/reflex-components-radix/src/reflex_components_radix/primitives/__init__.pyi": "bbd4d1a4fa73275a882c33ba485d0165",
Expand Down
55 changes: 55 additions & 0 deletions tests/units/compiler/test_memoize_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from reflex_components_core.el.elements.scripts import Noscript, Script
from reflex_components_core.el.elements.tables import Col
from reflex_components_core.el.elements.typography import Hr
from reflex_components_moment.moment import Moment
from reflex_components_radix.themes.layout.box import Box

import reflex as rx
Expand Down Expand Up @@ -1588,6 +1589,60 @@ def test_title_with_stateful_var_child_does_not_wrap_bare_independently() -> Non
)


def test_moment_with_stateful_var_child_does_not_wrap_bare_independently() -> None:
"""``rx.moment(state_var)`` must not produce a Bare component child.

react-moment feeds its children into ``moment()``; a memo-wrapped child
element parses like ``moment({})`` — today at midnight.
"""
moment = Moment.create(STATE_VAR, format="DD.MM.YYYY HH:mm:ss")
ctx, page_ctx = _compile_single_page(lambda: moment)

assert len(ctx.memoize_wrappers) == 1, (
"Expected exactly one snapshot wrapper for the moment; got: "
f"{list(ctx.memoize_wrappers)}"
)
wrapper_tag = next(iter(ctx.memoize_wrappers))
assert wrapper_tag.lower().startswith("moment_"), (
f"Wrapper should be derived from Moment, got: {wrapper_tag!r}"
)
output = page_ctx.output_code
assert output is not None
assert f"jsx({wrapper_tag}," in output, (
"The page output must call the snapshot wrapper.\n"
f"Page output snippet: {output[:2000]}"
)
assert "useTestState" not in output, (
"The state-bearing hook should live inside the memo body, not the page.\n"
f"Page output snippet: {output[:2000]}"
)
assert "TestState" not in output, (
"The state-context wiring should live inside the memo body, not the page.\n"
f"Page output snippet: {output[:2000]}"
)


def test_moment_memo_body_renders_text_interpolation_not_bare_component() -> None:
"""The moment's memo body must interpolate the state Var as text, not a Bare wrapper."""
ctx, _page_ctx = _compile_single_page(
lambda: Moment.create(STATE_VAR, format="DD.MM.YYYY HH:mm:ss")
)
memo_code = _compile_memo_module_text(ctx)

assert "jsx(Moment" in memo_code, (
"Moment snapshot body should contain a literal ``jsx(Moment, …)`` "
f"call. Memo code:\n{memo_code[:2000]}"
)
assert "useTestState" in memo_code, (
"Moment memo body should carry the stateful hook so the Bare child is "
f"interpolated inline, not lifted out.\nMemo code:\n{memo_code[:2000]}"
)
assert "Bare_" not in memo_code, (
"Moment's child must render as a text interpolation, not a Bare "
f"component wrapper.\nMemo code:\n{memo_code[:2000]}"
)


def test_meta_with_stateful_var_child_does_not_wrap_bare_independently() -> None:
"""``rx.el.meta(state_var)`` must not produce a Bare component child.

Expand Down
Loading