From 20e8b83421fa53cb83a4c57f7cbaa01ff110d33f Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 3 Feb 2025 17:21:00 -0800 Subject: [PATCH] [ENG-4570] Fix rx.foreach over dict (#4743) * Add test case for literal dict in foreach * [ENG-4570] Iterate over ObjectVar.entries * Adjust expectations of test_foreach.py unit tests --- .../.templates/jinja/web/pages/utils.js.jinja2 | 2 +- reflex/components/core/foreach.py | 5 +++++ tests/integration/test_var_operations.py | 17 +++++++++++++++++ tests/units/components/core/test_foreach.py | 16 ++++++++-------- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/reflex/.templates/jinja/web/pages/utils.js.jinja2 b/reflex/.templates/jinja/web/pages/utils.js.jinja2 index 08aeb0d38..c883dadcb 100644 --- a/reflex/.templates/jinja/web/pages/utils.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/utils.js.jinja2 @@ -60,7 +60,7 @@ {# Args: #} {# component: component dictionary #} {% macro render_iterable_tag(component) %} -<>{ {%- if component.iterable_type == 'dict' -%}Object.entries({{- component.iterable_state }}){%- else -%}{{- component.iterable_state }}{%- endif -%}.map(({{ component.arg_name }}, {{ component.arg_index }}) => ( +<>{ {{ component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => ( {% for child in component.children %} {{ render(child) }} {% endfor %} diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index 927b01333..e9222b200 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -54,6 +54,8 @@ class Foreach(Component): TypeError: If the render function is a ComponentState. UntypedVarError: If the iterable is of type Any without a type annotation. """ + from reflex.vars.object import ObjectVar + iterable = LiteralVar.create(iterable) if iterable._var_type == Any: raise ForeachVarError( @@ -70,6 +72,9 @@ class Foreach(Component): "Using a ComponentState as `render_fn` inside `rx.foreach` is not supported yet." ) + if isinstance(iterable, ObjectVar): + iterable = iterable.entries() + component = cls( iterable=iterable, render_fn=render_fn, diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 9b952c575..a5a74c9ee 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -605,6 +605,20 @@ def VarOperations(): rx.box(rx.foreach(range(42, 80, 3), rx.text.span), id="range_in_foreach2"), rx.box(rx.foreach(range(42, 20, -6), rx.text.span), id="range_in_foreach3"), rx.box(rx.foreach(range(42, 43, 5), rx.text.span), id="range_in_foreach4"), + # Literal dict in a foreach + rx.box(rx.foreach({"a": 1, "b": 2}, rx.text.span), id="dict_in_foreach1"), + # State Var dict in a foreach + rx.box( + rx.foreach(VarOperationState.dict1, rx.text.span), + id="dict_in_foreach2", + ), + rx.box( + rx.foreach( + VarOperationState.dict1.merge(VarOperationState.dict2), + rx.text.span, + ), + id="dict_in_foreach3", + ), ) @@ -809,6 +823,9 @@ def test_var_operations(driver, var_operations: AppHarness): ("range_in_foreach2", "42454851545760636669727578"), ("range_in_foreach3", "42363024"), ("range_in_foreach4", "42"), + ("dict_in_foreach1", "a1b2"), + ("dict_in_foreach2", "12"), + ("dict_in_foreach3", "1234"), ] for tag, expected in tests: diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index 094f6029d..48fae85e8 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -170,32 +170,32 @@ seen_index_vars = set() ForEachState.primary_color, display_primary_colors, { - "iterable_state": f"{ForEachState.get_full_name()}.primary_color", - "iterable_type": "dict", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color)", + "iterable_type": "list", }, ), ( ForEachState.color_with_shades, display_color_with_shades, { - "iterable_state": f"{ForEachState.get_full_name()}.color_with_shades", - "iterable_type": "dict", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades)", + "iterable_type": "list", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades, { - "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades", - "iterable_type": "dict", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)", + "iterable_type": "list", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades_v2, { - "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_with_shades", - "iterable_type": "dict", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)", + "iterable_type": "list", }, ), (