Skip to content

Commit 6d8b083

Browse files
authored
avoid runtime errors on iterating over non-array (#5796)
* avoid runtime errors on iterating over non-array * add test * handle object keys values entries more gracefully if they are not a dict
1 parent ba4070d commit 6d8b083

File tree

5 files changed

+40
-31
lines changed

5 files changed

+40
-31
lines changed

reflex/compiler/templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def render_iterable_tag(component: Any) -> str:
8686
children_rendered = "".join(
8787
[_RenderUtils.render(child) for child in component.get("children", [])]
8888
)
89-
return f"{component['iterable_state']}.map(({component['arg_name']},{component['arg_index']})=>({children_rendered}))"
89+
return f"Array.prototype.map.call({component['iterable_state']} ?? [],(({component['arg_name']},{component['arg_index']})=>({children_rendered})))"
9090

9191
@staticmethod
9292
def render_match_tag(component: Any) -> str:

reflex/vars/object.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -479,14 +479,9 @@ def object_keys_operation(value: ObjectVar):
479479
Returns:
480480
The keys of the object.
481481
"""
482-
if not types.is_optional(value._var_type):
483-
return var_operation_return(
484-
js_expression=f"Object.keys({value})",
485-
var_type=list[str],
486-
)
487482
return var_operation_return(
488-
js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.keys(value))({value})",
489-
var_type=(list[str] | None),
483+
js_expression=f"Object.keys({value} ?? {{}})",
484+
var_type=list[str],
490485
)
491486

492487

@@ -500,14 +495,9 @@ def object_values_operation(value: ObjectVar):
500495
Returns:
501496
The values of the object.
502497
"""
503-
if not types.is_optional(value._var_type):
504-
return var_operation_return(
505-
js_expression=f"Object.values({value})",
506-
var_type=list[value._value_type()],
507-
)
508498
return var_operation_return(
509-
js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.values(value))({value})",
510-
var_type=(list[value._value_type()] | None),
499+
js_expression=f"Object.values({value} ?? {{}})",
500+
var_type=list[value._value_type()],
511501
)
512502

513503

@@ -521,14 +511,9 @@ def object_entries_operation(value: ObjectVar):
521511
Returns:
522512
The entries of the object.
523513
"""
524-
if not types.is_optional(value._var_type):
525-
return var_operation_return(
526-
js_expression=f"Object.entries({value})",
527-
var_type=list[tuple[str, value._value_type()]],
528-
)
529514
return var_operation_return(
530-
js_expression=f"((value) => value ?? undefined === undefined ? undefined : Object.entries(value))({value})",
531-
var_type=(list[tuple[str, value._value_type()]] | None),
515+
js_expression=f"Object.entries({value} ?? {{}})",
516+
var_type=list[tuple[str, value._value_type()]],
532517
)
533518

534519

tests/integration/test_var_operations.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,29 @@ def index():
575575
rx.text(ArrayVar.range(2, 10, 2).join(","), id="list_join_range2"),
576576
rx.text(ArrayVar.range(5, 0, -1).join(","), id="list_join_range3"),
577577
rx.text(ArrayVar.range(0, 3).join(","), id="list_join_range4"),
578+
rx.box(
579+
# Test that foreach works with various non-array inputs without throwing
580+
rx.foreach(
581+
rx.Var("undefined").to(list),
582+
rx.text.span,
583+
),
584+
rx.foreach(
585+
rx.Var("null").to(list),
586+
rx.text.span,
587+
),
588+
rx.foreach(
589+
rx.Var("({})").to(list),
590+
rx.text.span,
591+
),
592+
rx.foreach(
593+
rx.Var("2").to(list),
594+
rx.text.span,
595+
),
596+
rx.foreach(
597+
rx.Var("false").to(list),
598+
rx.text.span,
599+
),
600+
),
578601
rx.box(
579602
rx.foreach(
580603
ArrayVar.range(0, 2),

tests/units/components/core/test_foreach.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,28 +171,28 @@ def display_color_index_tuple(color):
171171
ForEachState.primary_color,
172172
display_primary_colors,
173173
{
174-
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color{FIELD_MARKER})",
174+
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color{FIELD_MARKER} ?? {{}})",
175175
},
176176
),
177177
(
178178
ForEachState.color_with_shades,
179179
display_color_with_shades,
180180
{
181-
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades{FIELD_MARKER})",
181+
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades{FIELD_MARKER} ?? {{}})",
182182
},
183183
),
184184
(
185185
ForEachState.nested_colors_with_shades,
186186
display_nested_color_with_shades,
187187
{
188-
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})",
188+
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER} ?? {{}})",
189189
},
190190
),
191191
(
192192
ForEachState.nested_colors_with_shades,
193193
display_nested_color_with_shades_v2,
194194
{
195-
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})",
195+
"iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER} ?? {{}})",
196196
},
197197
),
198198
(

tests/units/test_var.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,15 +1079,16 @@ def test_object_operations():
10791079
object_var = LiteralObjectVar.create({"a": 1, "b": 2, "c": 3})
10801080

10811081
assert (
1082-
str(object_var.keys()) == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
1082+
str(object_var.keys())
1083+
== 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})'
10831084
)
10841085
assert (
10851086
str(object_var.values())
1086-
== 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
1087+
== 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})'
10871088
)
10881089
assert (
10891090
str(object_var.entries())
1090-
== 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
1091+
== 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {})'
10911092
)
10921093
assert str(object_var.a) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
10931094
assert str(object_var["a"]) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
@@ -1129,11 +1130,11 @@ def test_type_chains():
11291130
)
11301131
assert (
11311132
str(object_var.keys()[0].upper())
1132-
== 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(0).toUpperCase()'
1133+
== 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {}).at(0).toUpperCase()'
11331134
)
11341135
assert (
11351136
str(object_var.entries()[1][1] - 1)
1136-
== '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(1).at(1) - 1)'
1137+
== '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }) ?? {}).at(1).at(1) - 1)'
11371138
)
11381139
assert (
11391140
str(object_var["c"] + object_var["b"]) # pyright: ignore [reportCallIssue, reportOperatorIssue]

0 commit comments

Comments
 (0)