Skip to content

Commit 6c56313

Browse files
committed
docs(examples): make every documented handler a coroutine
Handlers must be coroutines now, so each example hoists its one-liner into a named 'async def' (or a factory), reads event data with 'await event.<path>', and batches sibling reads through 'event.read(...)' or asyncio.gather so the round trip is paid once.
1 parent b278ab4 commit 6c56313

189 files changed

Lines changed: 1047 additions & 504 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/examples/choosing_the_state_structure/coordinates_grouped/app.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
def marker():
2020
position, set_position = use_state({"x": 20, "y": 20})
2121

22-
def move_diagonally(event):
22+
async def move_diagonally(event):
2323
# One setter, one object: there is no second call left to forget.
2424
set_position({"x": position["x"] + STEP, "y": position["y"] + STEP})
2525

26-
def reset(event):
26+
async def reset(event):
2727
set_position({"x": 20, "y": 20})
2828

2929
return html.div(

‎docs/examples/choosing_the_state_structure/coordinates_separate/app.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ def marker():
2020
x, set_x = use_state(20)
2121
y, set_y = use_state(20)
2222

23-
def move_diagonally(event):
23+
async def move_diagonally(event):
2424
# The classic mistake: this handler remembers x and forgets y.
2525
set_x(x + STEP)
2626

27-
def move_correctly(event):
27+
async def move_correctly(event):
2828
set_x(x + STEP)
2929
set_y(y + STEP)
3030

31-
def reset(event):
31+
async def reset(event):
3232
set_x(20)
3333
set_y(20)
3434

‎docs/examples/choosing_the_state_structure/feedback_flags/app.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@ async def submit_with_bug(event):
3636
html.p({"id": "flags"}, f"is_sending={is_sending} is_sent={is_sent}"),
3737
)
3838

39+
async def handle_text_change(event):
40+
set_text(await event.target.value)
41+
3942
return html.div(
4043
html.p("How was your stay at The Prancing Pony?"),
4144
html.textarea(
4245
{
4346
"value": text,
44-
"onChange": lambda event: set_text(event["target"]["value"]),
47+
"onChange": handle_text_change,
4548
"disabled": is_sending,
4649
}
4750
),

‎docs/examples/choosing_the_state_structure/feedback_status/app.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ async def submit(event):
2525
if is_sent:
2626
return html.div(html.h3("Thanks for your feedback!"))
2727

28+
async def handle_text_change(event):
29+
set_text(await event.target.value)
30+
2831
return html.div(
2932
html.p("How was your stay at The Prancing Pony?"),
3033
html.textarea(
3134
{
3235
"value": text,
33-
"onChange": lambda event: set_text(event["target"]["value"]),
36+
"onChange": handle_text_change,
3437
"disabled": is_sending,
3538
}
3639
),

‎docs/examples/choosing_the_state_structure/full_name_derived/app.py‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,26 @@ def check_in():
1212
# Not state. Every render recomputes it from whatever the two fields hold.
1313
full_name = f"{first_name} {last_name}"
1414

15-
def rename_only_first(event):
15+
async def rename_only_first(event):
1616
set_first_name("Grace")
1717

18-
def rename_only_last(event):
18+
async def rename_only_last(event):
1919
set_last_name("Hopper")
2020

21+
async def handle_first_name_change(event):
22+
set_first_name(await event.target.value)
23+
24+
async def handle_last_name_change(event):
25+
set_last_name(await event.target.value)
26+
2127
return html.div(
2228
html.h2("Let's check you in"),
2329
html.label(
2430
"First name: ",
2531
html.input(
2632
{
2733
"value": first_name,
28-
"onChange": lambda event: set_first_name(event["target"]["value"]),
34+
"onChange": handle_first_name_change,
2935
}
3036
),
3137
),
@@ -35,7 +41,7 @@ def rename_only_last(event):
3541
html.input(
3642
{
3743
"value": last_name,
38-
"onChange": lambda event: set_last_name(event["target"]["value"]),
44+
"onChange": handle_last_name_change,
3945
}
4046
),
4147
),

‎docs/examples/choosing_the_state_structure/full_name_stored/app.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ def check_in():
1010
last_name, set_last_name = use_state("Lovelace")
1111
full_name, set_full_name = use_state("Ada Lovelace")
1212

13-
def handle_first_name_change(event):
14-
set_first_name(event["target"]["value"])
15-
set_full_name(event["target"]["value"] + " " + last_name)
13+
async def handle_first_name_change(event):
14+
set_first_name(await event.target.value)
15+
set_full_name(await event.target.value + " " + last_name)
1616

17-
def handle_last_name_change(event):
18-
set_last_name(event["target"]["value"])
19-
set_full_name(first_name + " " + event["target"]["value"])
17+
async def handle_last_name_change(event):
18+
set_last_name(await event.target.value)
19+
set_full_name(first_name + " " + await event.target.value)
2020

21-
def rename_only_first(event):
21+
async def rename_only_first(event):
2222
# The bug the extra state variable invites: one writer, one update.
2323
set_first_name("Grace")
2424

25-
def rename_only_last(event):
25+
async def rename_only_last(event):
2626
# This one does not touch full_name either.
2727
set_last_name("Hopper")
2828

‎docs/examples/choosing_the_state_structure/places_flat/app.py‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77

88
# start
9+
def bind_visit(on_visit, place_id):
10+
# Handlers are called with the event alone, so the id is bound by a closure
11+
# rather than a lambda default — a handler has to be a coroutine, and a
12+
# coroutine cannot be a lambda. The default stays overridable so a parent's
13+
# bound handler can be re-bound for a deeper child.
14+
async def visit(event, target_id=place_id):
15+
await on_visit(event, target_id)
16+
17+
return visit
18+
19+
920
@component
1021
def place_tree(place_id, on_visit, visited):
1122
place = PLACES[place_id]
@@ -19,11 +30,7 @@ def place_tree(place_id, on_visit, visited):
1930
(
2031
html.ol(
2132
[
22-
place_tree(
23-
child_id,
24-
lambda event, cid=child_id: on_visit(event, cid),
25-
visited,
26-
)
33+
place_tree(child_id, bind_visit(on_visit, child_id), visited)
2734
for child_id in children
2835
]
2936
)
@@ -39,19 +46,15 @@ def travel_plan():
3946
# touches exactly one entry — there is no enclosing object to rebuild.
4047
visited, set_visited = use_state(set())
4148

42-
def toggle(event, place_id=0):
49+
async def toggle(event, place_id=0):
4350
if place_id not in visited:
4451
set_visited({*visited, place_id})
4552

4653
return html.div(
4754
html.h2("Places to visit"),
4855
html.ol(
4956
[
50-
place_tree(
51-
place_id,
52-
lambda event, pid=place_id: toggle(event, pid),
53-
visited,
54-
)
57+
place_tree(place_id, bind_visit(toggle, place_id), visited)
5558
for place_id in PLACES[0]["childIds"]
5659
]
5760
),

‎docs/examples/choosing_the_state_structure/places_nested/app.py‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,31 @@ def place_tree(place, on_visit):
2727
html.button(
2828
{"onClick": on_visit}, "Visited" if place.get("visited") else "Visit"
2929
),
30-
html.ol([place_tree(child, on_visit) for child in children])
31-
if children
32-
else None,
30+
(
31+
html.ol([place_tree(child, on_visit) for child in children])
32+
if children
33+
else None
34+
),
3335
)
3436

3537

3638
@component
3739
def travel_plan():
3840
plan, set_plan = use_state(INITIAL_PLAN)
3941

42+
def make_visit(place_id):
43+
# A factory, because a handler has to be a coroutine and a coroutine
44+
# cannot be a lambda — this is how the id gets bound instead.
45+
async def visit(event):
46+
set_plan(mark_visited(plan, place_id))
47+
48+
return visit
49+
4050
return html.div(
4151
html.h2("Places to visit"),
4252
html.ol(
4353
[
44-
place_tree(
45-
place,
46-
lambda event, pid=place["id"]: set_plan(mark_visited(plan, pid)),
47-
)
54+
place_tree(place, make_visit(place["id"]))
4855
for place in plan["childPlaces"]
4956
]
5057
),

‎docs/examples/choosing_the_state_structure/snacks_duplicated/app.py‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,27 @@ def menu():
1515
items, set_items = use_state(INITIAL_ITEMS)
1616
selected_item, set_selected_item = use_state(INITIAL_ITEMS[0])
1717

18-
def handle_item_change(item_id, event):
19-
new_title = event["target"]["value"]
18+
async def handle_item_change(item_id, event):
19+
new_title = await event.target.value
2020
set_items(
2121
[
2222
{**item, "title": new_title} if item["id"] == item_id else item
2323
for item in items
2424
]
2525
)
2626

27+
def make_change(item):
28+
async def change(event):
29+
await handle_item_change(item["id"], event)
30+
31+
return change
32+
33+
def make_choose(item):
34+
async def choose(event):
35+
set_selected_item(item)
36+
37+
return choose
38+
2739
return html.div(
2840
html.h2("What's your travel snack?"),
2941
html.ul(
@@ -33,13 +45,11 @@ def handle_item_change(item_id, event):
3345
html.input(
3446
{
3547
"value": item["title"],
36-
"onChange": lambda event, i=item: handle_item_change(
37-
i["id"], event
38-
),
48+
"onChange": make_change(item),
3949
}
4050
),
4151
html.button(
42-
{"onClick": lambda event, i=item: set_selected_item(i)},
52+
{"onClick": make_choose(item)},
4353
"Choose",
4454
),
4555
)

‎docs/examples/choosing_the_state_structure/snacks_normalized/app.py‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,27 @@ def menu():
1818
# Derived: there is exactly one copy of the title, and it lives in items.
1919
selected_item = next(item for item in items if item["id"] == selected_id)
2020

21-
def handle_item_change(item_id, event):
22-
new_title = event["target"]["value"]
21+
async def handle_item_change(item_id, event):
22+
new_title = await event.target.value
2323
set_items(
2424
[
2525
{**item, "title": new_title} if item["id"] == item_id else item
2626
for item in items
2727
]
2828
)
2929

30+
def make_change(item_id):
31+
async def change(event):
32+
await handle_item_change(item_id, event)
33+
34+
return change
35+
36+
def make_choose(item_id):
37+
async def choose(event):
38+
set_selected_id(item_id)
39+
40+
return choose
41+
3042
return html.div(
3143
html.h2("What's your travel snack?"),
3244
html.ul(
@@ -36,13 +48,11 @@ def handle_item_change(item_id, event):
3648
html.input(
3749
{
3850
"value": item["title"],
39-
"onChange": lambda event, i=item: handle_item_change(
40-
i["id"], event
41-
),
51+
"onChange": make_change(item["id"]),
4252
}
4353
),
4454
html.button(
45-
{"onClick": lambda event, i=item: set_selected_id(i["id"])},
55+
{"onClick": make_choose(item["id"])},
4656
"Choose",
4757
),
4858
)

0 commit comments

Comments
 (0)