Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/reflex-base/news/6637.misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`Component` gained a private `_get_tag_name()` helper returning the JS expression that references the component's tag (quoted for global-scope DOM tags without a library); `Component._render` and `DebounceInput` now share it instead of duplicating the quoting logic.
19 changes: 13 additions & 6 deletions packages/reflex-base/src/reflex_base/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,18 @@ def _exclude_props(self) -> list[str]:
"""
return []

def _get_tag_name(self) -> str:
"""Get the JS expression used to reference this component's tag.

Returns:
The alias (or tag) identifier, quoted as a string literal when the
tag is a global scope element like ``"input"``.
"""
name = (self.tag if not self.alias else self.alias) or ""
if self._is_tag_in_global_scope and self.library is None:
name = '"' + name + '"'
return name

def _render(self, props: dict[str, Any] | None = None) -> Tag:
"""Define how to render the component in React.

Expand All @@ -1181,14 +1193,9 @@ def _render(self, props: dict[str, Any] | None = None) -> Tag:
Returns:
The tag to render.
"""
# Create the base tag.
name = (self.tag if not self.alias else self.alias) or ""
if self._is_tag_in_global_scope and self.library is None:
name = '"' + name + '"'

# Create the base tag.
tag = Tag(
name=name,
name=self._get_tag_name(),
special_props=self.special_props.copy(),
)

Expand Down
1 change: 1 addition & 0 deletions packages/reflex-components-core/news/6637.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`rx.debounce_input` no longer crashes the page with `ReferenceError: input is not defined` when wrapping a native DOM element such as `rx.el.input` or `rx.el.textarea`. The `element` prop now passes global-scope tags as string literals (`element:"input"`), while library components keep referencing their imported identifiers.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def create(cls, *children: Component, **props: Any) -> Component:
props.setdefault(
"element",
Var(
_js_expr=str(child.alias or child.tag),
_js_expr=child._get_tag_name(),
_var_type=type[Component],
_var_data=VarData(
imports=child._get_imports(),
Expand Down
111 changes: 111 additions & 0 deletions tests/integration/tests_playwright/test_debounce_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Integration test for ``rx.debounce_input`` wrapping native ``rx.el`` elements.

Regression coverage for the frontend ``ReferenceError: input is not defined``
that fired when DebounceInput passed a native DOM tag (e.g. ``rx.el.input``)
as a bare identifier in the ``element`` prop instead of a string literal.
"""

from collections.abc import Generator

import pytest
from playwright.sync_api import Page, expect

from reflex.testing import AppHarness


def DebounceNativeElementApp():
"""App wrapping native DOM elements in rx.debounce_input."""
import reflex as rx

class State(rx.State):
text: str = ""
area: str = ""

@rx.event
def set_text(self, value: str):
self.text = value

@rx.event
def set_area(self, value: str):
self.area = value

@rx.page("/")
def index():
return rx.box(
rx.input(
value=State.router.session.client_token,
read_only=True,
id="token",
),
rx.debounce_input(
rx.el.input(
placeholder="Search...",
on_change=State.set_text,
id="native-input",
),
debounce_timeout=50,
),
rx.debounce_input(
rx.el.textarea(
on_change=State.set_area,
id="native-textarea",
),
debounce_timeout=50,
),
rx.text(State.text, id="text-value"),
rx.text(State.area, id="area-value"),
)

app = rx.App() # noqa: F841


@pytest.fixture(scope="module")
def debounce_native_element_app(
tmp_path_factory,
) -> Generator[AppHarness, None, None]:
"""Start DebounceNativeElementApp via AppHarness.

Args:
tmp_path_factory: pytest fixture for creating temporary directories.

Yields:
Running AppHarness instance.
"""
with AppHarness.create(
root=tmp_path_factory.mktemp("debounce_native_element_app"),
app_source=DebounceNativeElementApp,
) as harness:
assert harness.app_instance is not None, "app is not running"
yield harness


def test_debounce_wrapped_native_elements(
debounce_native_element_app: AppHarness, page: Page
):
"""Debounced native input/textarea render and propagate changes to state.

Args:
debounce_native_element_app: AppHarness running the test app.
page: Playwright page.
"""
assert debounce_native_element_app.frontend_url is not None

page_errors: list[str] = []
page.on("pageerror", lambda exc: page_errors.append(str(exc)))

page.goto(debounce_native_element_app.frontend_url)
expect(page.locator("#token")).not_to_have_value("")

# DebounceInput must render the actual native tags with carried props.
native_input = page.locator("input#native-input")
expect(native_input).to_have_attribute("placeholder", "Search...")
native_textarea = page.locator("textarea#native-textarea")
expect(native_textarea).to_be_visible()

native_input.fill("hello")
expect(page.locator("#text-value")).to_have_text("hello")

native_textarea.fill("world")
expect(page.locator("#area-value")).to_have_text("world")

assert not page_errors, f"Frontend raised unexpected errors: {page_errors}"
15 changes: 15 additions & 0 deletions tests/units/components/core/test_debounce.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ def test_render_with_special_props():
assert next(iter(tag.special_props)).equals(special_prop)


def test_render_native_element_child():
"""DebounceInput quotes the element prop for native DOM element children."""
tag = rx.debounce_input(rx.el.input(on_change=S.on_change))._render()
assert tag.props["element"]._js_expr == '"input"'

tag = rx.debounce_input(rx.el.textarea(on_change=S.on_change))._render()
assert tag.props["element"]._js_expr == '"textarea"'


def test_render_library_element_child():
"""DebounceInput references a library component child by its identifier."""
tag = rx.debounce_input(rx.input(on_change=S.on_change))._render()
assert tag.props["element"]._js_expr == "RadixThemesTextField.Root"


def test_event_triggers():
debounced_input = rx.debounce_input(
rx.input(
Expand Down
Loading