Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions reacton/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2827,10 +2827,13 @@ def _reconsolidate(self, el: Element, default_key: str, parent_key: str):
else:
widget_previous = context.widgets[key]

# effects of child components run while we walk the kwargs, and they may
# change this element's kwargs (like the default renderer, walk a copy)
el_kwargs = el.kwargs.copy()
orphan_ids: Set[str] = set()
if widget_previous is None:
# initial create
kwargs = self._visit_children_values(el.kwargs, key, parent_key, self._reconsolidate)
kwargs = self._visit_children_values(el_kwargs, key, parent_key, self._reconsolidate)
if el.is_shared and el in self._shared_widgets:
raise RuntimeError(f"Element ({el}) was already in self._shared_widgets")
widget = None
Expand All @@ -2844,13 +2847,13 @@ def _reconsolidate(self, el: Element, default_key: str, parent_key: str):
self._store_widget(context, el, key, widget)
elif el_prev is not None and el_prev.component == el.component:
# update the existing widget in place
kwargs = self._visit_children_values(el.kwargs, key, parent_key, self._reconsolidate)
kwargs = self._visit_children_values(el_kwargs, key, parent_key, self._reconsolidate)
if not context.exceptions_children:
# the same element whose kwargs hold no elements: nothing can have changed.
# With elements (a container), the kwargs resolve to widgets, so compare
# them with what the widget holds: equal means setting them is a no-op
# (and a value changed from the frontend is still set back)
if el is not el_prev or not (_values_identical(kwargs, el.kwargs) or _widget_holds_values(widget_previous, kwargs)):
if el is not el_prev or not (_values_identical(kwargs, el_kwargs) or _widget_holds_values(widget_previous, kwargs)):
try:
el._update_widget(widget_previous, el_prev, kwargs)
except BaseException as e:
Expand All @@ -2862,7 +2865,7 @@ def _reconsolidate(self, el: Element, default_key: str, parent_key: str):
assert el_prev is not None, "widget_previous is not None, but el_prev is"
# a different widget type at the same key: replace
self._remove_element(el_prev, key, parent_key=parent_key)
kwargs = self._visit_children_values(el.kwargs, key, parent_key, self._reconsolidate)
kwargs = self._visit_children_values(el_kwargs, key, parent_key, self._reconsolidate)
widget = None
if not context.exceptions_children:
try:
Expand Down
30 changes: 30 additions & 0 deletions reacton/core_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3973,3 +3973,33 @@ def click_during_render():
assert isinstance(box.children[0], widgets.HTML)
assert "click boom" in box.children[0].value
rc.close()


def test_child_effect_changes_parent_element_kwargs():
# the effect runs while the parent's kwargs are walked; changing them (adding a key)
# must not break that walk
set_count = lambda x: None # noqa
holder: dict = {}

@react.component
def Child():
def effect():
holder["box"].kwargs.update(box_style="success")

use_effect(effect)
return w.Label(value="child")

@react.component
def App():
nonlocal set_count
count, set_count = react.use_state(0)
holder["box"] = w.VBox(children=[Child(), w.Label(value=str(count))])
return holder["box"]

box, rc = react.render(App(), handle_error=False)
vbox = box.children[0]
assert vbox.children[0].value == "child"
set_count(1)
assert box.children[0] is vbox
assert vbox.children[1].value == "1"
rc.close()
Loading