Browse Source

[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
Masen Furer 3 months ago
parent
commit
3778d774d0

+ 1 - 1
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 %}

+ 5 - 0
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,

+ 17 - 0
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:

+ 8 - 8
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",
             },
         ),
         (