From ee110b8517f3affe3ae11fd75947fae2337c1432 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 16 Sep 2025 12:11:29 -0700 Subject: [PATCH 1/3] avoid runtime errors on iterating over non-array --- reflex/compiler/templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py index 923e0225f27..8e325bee191 100644 --- a/reflex/compiler/templates.py +++ b/reflex/compiler/templates.py @@ -86,7 +86,7 @@ def render_iterable_tag(component: Any) -> str: children_rendered = "".join( [_RenderUtils.render(child) for child in component.get("children", [])] ) - return f"{component['iterable_state']}.map(({component['arg_name']},{component['arg_index']})=>({children_rendered}))" + return f"Array.prototype.map.call({component['iterable_state']} ?? [],(({component['arg_name']},{component['arg_index']})=>({children_rendered})))" @staticmethod def render_match_tag(component: Any) -> str: From f8377b23c0e177f3ad9e3a00fd63743f7baea0a4 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 16 Sep 2025 12:23:18 -0700 Subject: [PATCH 2/3] add test --- tests/integration/test_var_operations.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/integration/test_var_operations.py b/tests/integration/test_var_operations.py index 6a32bc67718..f36a463b445 100644 --- a/tests/integration/test_var_operations.py +++ b/tests/integration/test_var_operations.py @@ -575,6 +575,29 @@ def index(): rx.text(ArrayVar.range(2, 10, 2).join(","), id="list_join_range2"), rx.text(ArrayVar.range(5, 0, -1).join(","), id="list_join_range3"), rx.text(ArrayVar.range(0, 3).join(","), id="list_join_range4"), + rx.box( + # Test that foreach works with various non-array inputs without throwing + rx.foreach( + rx.Var("undefined").to(list), + rx.text.span, + ), + rx.foreach( + rx.Var("null").to(list), + rx.text.span, + ), + rx.foreach( + rx.Var("({})").to(list), + rx.text.span, + ), + rx.foreach( + rx.Var("2").to(list), + rx.text.span, + ), + rx.foreach( + rx.Var("false").to(list), + rx.text.span, + ), + ), rx.box( rx.foreach( ArrayVar.range(0, 2), From 1053753507939256d66bb3e766b264fcefb6dc0e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 17 Sep 2025 11:21:43 -0700 Subject: [PATCH 3/3] handle object keys values entries more gracefully if they are not a dict --- reflex/vars/object.py | 27 +++++---------------- tests/units/components/core/test_foreach.py | 8 +++--- tests/units/test_var.py | 11 +++++---- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/reflex/vars/object.py b/reflex/vars/object.py index 53f682e8ce6..ff7323f49d9 100644 --- a/reflex/vars/object.py +++ b/reflex/vars/object.py @@ -479,14 +479,9 @@ def object_keys_operation(value: ObjectVar): Returns: The keys of the object. """ - if not types.is_optional(value._var_type): - return var_operation_return( - js_expression=f"Object.keys({value})", - var_type=list[str], - ) return var_operation_return( - js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.keys(value))({value})", - var_type=(list[str] | None), + js_expression=f"Object.keys({value} ?? {{}})", + var_type=list[str], ) @@ -500,14 +495,9 @@ def object_values_operation(value: ObjectVar): Returns: The values of the object. """ - if not types.is_optional(value._var_type): - return var_operation_return( - js_expression=f"Object.values({value})", - var_type=list[value._value_type()], - ) return var_operation_return( - js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.values(value))({value})", - var_type=(list[value._value_type()] | None), + js_expression=f"Object.values({value} ?? {{}})", + var_type=list[value._value_type()], ) @@ -521,14 +511,9 @@ def object_entries_operation(value: ObjectVar): Returns: The entries of the object. """ - if not types.is_optional(value._var_type): - return var_operation_return( - js_expression=f"Object.entries({value})", - var_type=list[tuple[str, value._value_type()]], - ) return var_operation_return( - js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.entries(value))({value})", - var_type=(list[tuple[str, value._value_type()]] | None), + js_expression=f"Object.entries({value} ?? {{}})", + var_type=list[tuple[str, value._value_type()]], ) diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index 19cc3ad4594..35fdcdbee9d 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -171,28 +171,28 @@ def display_color_index_tuple(color): ForEachState.primary_color, display_primary_colors, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color{FIELD_MARKER})", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color{FIELD_MARKER} ?? {{}})", }, ), ( ForEachState.color_with_shades, display_color_with_shades, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades{FIELD_MARKER})", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades{FIELD_MARKER} ?? {{}})", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER} ?? {{}})", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades_v2, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER} ?? {{}})", }, ), ( diff --git a/tests/units/test_var.py b/tests/units/test_var.py index a710e4409c2..13bf13d9a26 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1079,15 +1079,16 @@ def test_object_operations(): object_var = LiteralObjectVar.create({"a": 1, "b": 2, "c": 3}) assert ( - str(object_var.keys()) == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' + str(object_var.keys()) + == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})' ) assert ( str(object_var.values()) - == 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' + == 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})' ) assert ( str(object_var.entries()) - == 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' + == 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})' ) assert str(object_var.a) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]' assert str(object_var["a"]) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]' @@ -1129,11 +1130,11 @@ def test_type_chains(): ) assert ( str(object_var.keys()[0].upper()) - == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(0).toUpperCase()' + == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {}).at(0).toUpperCase()' ) assert ( str(object_var.entries()[1][1] - 1) - == '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(1).at(1) - 1)' + == '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {}).at(1).at(1) - 1)' ) assert ( str(object_var["c"] + object_var["b"]) # pyright: ignore [reportCallIssue, reportOperatorIssue]