Skip to content

Commit 754a6d1

Browse files
feat: batch updates in event handlers
In 9502163 and 03a9b39 we implemented batch updates from on_<name> change handlers. This caused a change handler that set multiple states to be changed to not trigger a render multiple times. This behaviour was not implemented for event handlers. This commit implements this behaviour for event handlers as well.
1 parent 16518bf commit 754a6d1

File tree

3 files changed

+55
-21
lines changed

3 files changed

+55
-21
lines changed

reacton/core.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def close_widget(widget: widgets.Widget):
138138
logger.warning("Widget %r does not have a close method, possibly a close trait was added", widget)
139139

140140

141-
def _event_handler_exception_wrapper(f):
141+
def _event_handler_exception_wrapper_and_batch(f):
142142
"""Wrap an event handler to catch exceptions and put them in a reacton context.
143143
144144
This allows a component to catch the exception of a direct child"""
@@ -148,7 +148,8 @@ def _event_handler_exception_wrapper(f):
148148

149149
def wrapper(*args, **kwargs):
150150
try:
151-
f(*args, **kwargs)
151+
with rc:
152+
f(*args, **kwargs)
152153
except Exception as e:
153154
assert context is not None
154155
# because widgets don't have a context, but are a child of a component
@@ -160,6 +161,10 @@ def wrapper(*args, **kwargs):
160161
return wrapper
161162

162163

164+
# for backwards compatibility
165+
_event_handler_exception_wrapper = _event_handler_exception_wrapper_and_batch
166+
167+
163168
def join_key(parent_key, key):
164169
return f"{parent_key}{key}"
165170

reacton/core_test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3108,6 +3108,47 @@ def Test():
31083108
rc.close()
31093109

31103110

3111+
def test_batch_update_from_event():
3112+
@reacton.component
3113+
def Test():
3114+
state, set_state = reacton.use_state(0)
3115+
3116+
def increment_twice():
3117+
set_state(2)
3118+
set_state(3)
3119+
3120+
w.Button(description=str(state), on_click=increment_twice)
3121+
3122+
box, rc = react.render(Test(), handle_error=False)
3123+
assert rc.find(widgets.Button).widget.description == "0"
3124+
assert rc.render_count == 1
3125+
rc.find(widgets.Button).widget.click()
3126+
assert rc.render_count == 2
3127+
assert rc.find(widgets.Button).widget.description == "3"
3128+
rc.close()
3129+
3130+
3131+
def test_batch_update_from_event_vue():
3132+
@reacton.component
3133+
def Test():
3134+
state, set_state = reacton.use_state(0)
3135+
3136+
def increment_twice():
3137+
set_state(2)
3138+
set_state(3)
3139+
3140+
btn = v.Btn(children=[str(state)], on_click=increment_twice)
3141+
v.use_event(btn, "click", lambda *_ignore: increment_twice())
3142+
3143+
box, rc = react.render(Test(), handle_error=False)
3144+
assert rc.find(ipyvuetify.Btn).widget.children[0] == "0"
3145+
assert rc.render_count == 1
3146+
rc.find(ipyvuetify.Btn).widget.fire_event("click", {})
3147+
assert rc.render_count == 2
3148+
assert rc.find(ipyvuetify.Btn).widget.children[0] == "3"
3149+
rc.close()
3150+
3151+
31113152
def test_event_multiple():
31123153
@reacton.component
31133154
def Test():

reacton/ipyvue.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import ipyvue
44

55
import reacton as react
6-
from reacton.core import get_render_context
6+
from reacton.core import _event_handler_exception_wrapper_and_batch
77

88

99
def use_event(el: react.core.Element, event_and_modifiers, callback: Callable[[Any], Any]):
@@ -13,26 +13,14 @@ def use_event(el: react.core.Element, event_and_modifiers, callback: Callable[[A
1313

1414
def add_event_handler():
1515
vue_widget = cast(ipyvue.VueWidget, react.core.get_widget(el))
16-
# we are basically copying the logic from reacton.core._event_handler_exception_wrapper
17-
rc = get_render_context()
18-
context = rc.context
19-
assert context is not None
20-
21-
def handler(*args):
22-
try:
23-
callback_ref.current(*args)
24-
except Exception as e:
25-
assert context is not None
26-
# because widgets don't have a context, but are a child of a component
27-
# we add it to exceptions_children, not exception_self
28-
# this allows a component to catch the exception of a direct child
29-
context.exceptions_children.append(e)
30-
rc.force_update()
31-
32-
vue_widget.on_event(event_and_modifiers, handler)
16+
17+
def wrapper(*args):
18+
callback_ref.current(*args)
19+
20+
vue_widget.on_event(event_and_modifiers, _event_handler_exception_wrapper_and_batch(wrapper))
3321

3422
def cleanup():
35-
vue_widget.on_event(event_and_modifiers, handler, remove=True)
23+
vue_widget.on_event(event_and_modifiers, wrapper, remove=True)
3624

3725
return cleanup
3826

0 commit comments

Comments
 (0)