From 2c3257d4ea1bae113a0b2338e2b7763189287206 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 29 Jan 2025 12:16:54 -0800 Subject: [PATCH] fix tag render to be recursive (#4714) --- .../jinja/web/pages/utils.js.jinja2 | 2 +- reflex/components/component.py | 4 +- reflex/components/tags/tag.py | 43 +++++++++++-------- tests/units/components/core/test_match.py | 12 +++--- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/reflex/.templates/jinja/web/pages/utils.js.jinja2 b/reflex/.templates/jinja/web/pages/utils.js.jinja2 index 567ca6e602b..08aeb0d389c 100644 --- a/reflex/.templates/jinja/web/pages/utils.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/utils.js.jinja2 @@ -86,7 +86,7 @@ {% for condition in case[:-1] %} case JSON.stringify({{ condition._js_expr }}): {% endfor %} - return {{ case[-1] }}; + return {{ render(case[-1]) }}; break; {% endfor %} default: diff --git a/reflex/components/component.py b/reflex/components/component.py index 3f1b88feadd..810104d47e3 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -2389,7 +2389,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> if tag["name"] == "match": element = tag["cond"] - conditionals = tag["default"] + conditionals = render_dict_to_var(tag["default"], imported_names) for case in tag["match_cases"][::-1]: condition = case[0].to_string() == element.to_string() @@ -2398,7 +2398,7 @@ def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> conditionals = ternary_operation( condition, - case[-1], + render_dict_to_var(case[-1], imported_names), conditionals, ) diff --git a/reflex/components/tags/tag.py b/reflex/components/tags/tag.py index 8a6326dc046..983726e5688 100644 --- a/reflex/components/tags/tag.py +++ b/reflex/components/tags/tag.py @@ -3,13 +3,33 @@ from __future__ import annotations import dataclasses -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Sequence, Union from reflex.event import EventChain from reflex.utils import format, types from reflex.vars.base import LiteralVar, Var +def render_prop(value: Any) -> Any: + """Render the prop. + + Args: + value: The value to render. + + Returns: + The rendered value. + """ + from reflex.components.component import BaseComponent + + if isinstance(value, BaseComponent): + return value.render() + if isinstance(value, Sequence) and not isinstance(value, str): + return [render_prop(v) for v in value] + if callable(value) and not isinstance(value, Var): + return None + return value + + @dataclasses.dataclass() class Tag: """A React tag.""" @@ -65,25 +85,10 @@ def __iter__(self): Yields: Tuple[str, Any]: The field name and value. """ - from reflex.components.component import BaseComponent - for field in dataclasses.fields(self): - value = getattr(self, field.name) - if isinstance(value, list): - children = [] - for child in value: - if isinstance(child, BaseComponent): - children.append(child.render()) - else: - children.append(child) - yield field.name, children - continue - if isinstance(value, BaseComponent): - yield field.name, value.render() - continue - if callable(value) and not isinstance(value, Var): - continue - yield field.name, getattr(self, field.name) + rendered_value = render_prop(getattr(self, field.name)) + if rendered_value is not None: + yield field.name, rendered_value def add_props(self, **kwargs: Optional[Any]) -> Tag: """Add props to the tag. diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index b765750eeb9..47652cd4368 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -42,7 +42,7 @@ def test_match_components(): assert match_cases[0][0]._js_expr == "1" assert match_cases[0][0]._var_type is int - first_return_value_render = match_cases[0][1].render() + first_return_value_render = match_cases[0][1] assert first_return_value_render["name"] == "RadixThemesText" assert first_return_value_render["children"][0]["contents"] == '{"first value"}' @@ -50,31 +50,31 @@ def test_match_components(): assert match_cases[1][0]._var_type is int assert match_cases[1][1]._js_expr == "3" assert match_cases[1][1]._var_type is int - second_return_value_render = match_cases[1][2].render() + second_return_value_render = match_cases[1][2] assert second_return_value_render["name"] == "RadixThemesText" assert second_return_value_render["children"][0]["contents"] == '{"second value"}' assert match_cases[2][0]._js_expr == "[1, 2]" assert match_cases[2][0]._var_type == List[int] - third_return_value_render = match_cases[2][1].render() + third_return_value_render = match_cases[2][1] assert third_return_value_render["name"] == "RadixThemesText" assert third_return_value_render["children"][0]["contents"] == '{"third value"}' assert match_cases[3][0]._js_expr == '"random"' assert match_cases[3][0]._var_type is str - fourth_return_value_render = match_cases[3][1].render() + fourth_return_value_render = match_cases[3][1] assert fourth_return_value_render["name"] == "RadixThemesText" assert fourth_return_value_render["children"][0]["contents"] == '{"fourth value"}' assert match_cases[4][0]._js_expr == '({ ["foo"] : "bar" })' assert match_cases[4][0]._var_type == Mapping[str, str] - fifth_return_value_render = match_cases[4][1].render() + fifth_return_value_render = match_cases[4][1] assert fifth_return_value_render["name"] == "RadixThemesText" assert fifth_return_value_render["children"][0]["contents"] == '{"fifth value"}' assert match_cases[5][0]._js_expr == f"({MatchState.get_name()}.num + 1)" assert match_cases[5][0]._var_type is int - fifth_return_value_render = match_cases[5][1].render() + fifth_return_value_render = match_cases[5][1] assert fifth_return_value_render["name"] == "RadixThemesText" assert fifth_return_value_render["children"][0]["contents"] == '{"sixth value"}'