Skip to content

Commit 52faa4f

Browse files
committed
element render functions do not need to be async
add use_async hook to compensate - this isolates async things to effects
1 parent 4df08a7 commit 52faa4f

34 files changed

+233
-216
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# IDOM
22

3+
<a href="https://github.com/idom-team/idom/actions?query=workflow%3ATest">
4+
<img alt="Tests" src="https://github.com/idom-team/idom/workflows/Test/badge.svg?event=push" />
5+
</a>
36
<a href="https://codecov.io/gh/rmorshea/idom">
47
<img alt="Code Coverage" src="https://codecov.io/gh/rmorshea/idom/branch/master/graph/badge.svg" />
58
</a>
@@ -45,7 +48,7 @@ IDOM can be used to create a simple slideshow which changes whenever a user clic
4548
import idom
4649

4750
@idom.element
48-
async def Slideshow():
51+
def Slideshow():
4952
index, set_index = idom.hooks.use_state(0)
5053
url = f"https://picsum.photos/800/300?image={index}"
5154
return idom.html.img({"src": url, "onClick": lambda event: set_index(index + 1)})

docs/source/core-concepts.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ whose body contains a hook usage. We'll demonstrate that with a simple
4545

4646

4747
@idom.element
48-
async def ClickCount():
48+
def ClickCount():
4949
count, set_count = idom.hooks.use_state(0)
5050

5151
return idom.html.button(
@@ -85,7 +85,7 @@ have to re-render the layout and see what changed:
8585

8686

8787
@idom.element
88-
async def ClickCount():
88+
def ClickCount():
8989
count, set_count = idom.hooks.use_state(0)
9090

9191
@idom.event(target_id=event_handler_id) # <-- trick to hard code event handler ID

docs/source/examples.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ view. In a Jupyter Notebook it will appear in an output cell. If you're running
3636
PerClientStateServer(element, *args, **kwargs).run("127.0.0.1", 8765)
3737
3838
@idom.element
39-
async def Main(self):
39+
def Main(self):
4040
# define your element here
4141
...
4242
@@ -52,7 +52,7 @@ view. In a Jupyter Notebook it will appear in an output cell. If you're running
5252
display = init_display("127.0.0.1")
5353
5454
@idom.element
55-
async def MyElement():
55+
def MyElement():
5656
# define your element here
5757
...
5858

docs/source/examples/click_count.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
@idom.element
5-
async def ClickCount():
5+
def ClickCount():
66
count, set_count = idom.hooks.use_state(0)
77

88
return idom.html.button(

docs/source/examples/custom_chart.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
@idom.element
22-
async def ShowChartClicks():
22+
def ShowChartClicks():
2323
last_event, set_last_event = idom.hooks.use_state({})
2424
return idom.html.div(
2525
ClickableChart(

docs/source/examples/matplotlib_plot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
@idom.element
10-
async def PolynomialPlot():
10+
def PolynomialPlot():
1111
coefficients, set_coefficients = idom.hooks.use_state([0])
1212

1313
x = [n for n in linspace(-1, 1, 50)]
@@ -20,7 +20,7 @@ async def PolynomialPlot():
2020

2121

2222
@idom.element
23-
async def ExpandableNumberInputs(values, set_values):
23+
def ExpandableNumberInputs(values, set_values):
2424
inputs = []
2525
for i in range(len(values)):
2626

docs/source/examples/primary_secondary_buttons.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def pre_seperated(*args):
1818

1919

2020
@idom.element
21-
async def PrimarySecondaryButtons():
21+
def PrimarySecondaryButtons():
2222
state, set_state = idom.hooks.use_state(
2323
{"message": None, "event": None, "info": None}
2424
)

docs/source/examples/simple_dashboard.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
@idom.element
13-
async def RandomWalk():
13+
def RandomWalk():
1414
mu = idom.hooks.use_ref(0)
1515
sigma = idom.hooks.use_ref(1)
1616

@@ -39,11 +39,11 @@ async def RandomWalk():
3939

4040

4141
@idom.element
42-
async def RandomWalkGraph(mu, sigma):
42+
def RandomWalkGraph(mu, sigma):
4343
interval = use_interval(0.5)
4444
data, set_data = idom.hooks.use_state([{"x": 0, "y": 0}] * 50)
4545

46-
@use_async_effect
46+
@idom.hooks.use_async
4747
async def animate():
4848
await interval
4949
last_data_point = data[-1]
@@ -57,7 +57,7 @@ async def animate():
5757

5858

5959
@idom.element
60-
async def NumberInput(label, value, set_value_callback, domain):
60+
def NumberInput(label, value, set_value_callback, domain):
6161
minimum, maximum, step = domain
6262
attrs = {"min": minimum, "max": maximum, "step": step}
6363

@@ -75,22 +75,14 @@ def update_value(value):
7575
)
7676

7777

78-
def use_async_effect(function):
79-
def ensure_effect_future():
80-
future = asyncio.ensure_future(function())
81-
return future.cancel
78+
def use_interval(rate: float) -> Awaitable[None]:
79+
usage_time = use_ref(time.time())
8280

83-
idom.hooks.use_effect(ensure_effect_future)
84-
85-
86-
def use_interval(rate):
87-
usage_time = idom.hooks.use_ref(time.time())
88-
89-
async def interval():
81+
async def interval() -> None:
9082
await asyncio.sleep(rate - (time.time() - usage_time.current))
9183
usage_time.current = time.time()
9284

93-
return interval()
85+
return asyncio.ensure_future(interval())
9486

9587

9688
display(RandomWalk)

docs/source/examples/slideshow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
@idom.element
5-
async def Slideshow():
5+
def Slideshow():
66
index, set_index = idom.hooks.use_state(0)
77

88
async def next_image(event):

docs/source/examples/snake_game.py

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,23 @@ class GameState(enum.Enum):
1414

1515

1616
@idom.element
17-
async def GameView(grid_size, block_scale):
17+
def GameView(grid_size, block_scale):
1818
game_state, set_game_state = idom.hooks.use_state(GameState.init)
1919

2020
if game_state == GameState.play:
2121
return GameLoop(grid_size, block_scale, set_game_state)
2222

23-
async def start_playing(event):
24-
set_game_state(GameState.play)
23+
start_button = idom.html.button(
24+
{"onClick": lambda event: set_game_state(GameState.play)},
25+
"Start",
26+
)
2527

2628
if game_state == GameState.won:
27-
await asyncio.sleep(1)
28-
return idom.html.div(
29-
idom.html.h1("You won!"),
30-
idom.html.button({"onClick": start_playing}, "Start"),
31-
)
29+
return idom.html.div(idom.html.h1("You won!"), start_button)
3230
elif game_state == GameState.lost:
33-
await asyncio.sleep(1)
34-
return idom.html.div(
35-
idom.html.h1("You lost"),
36-
idom.html.button({"onClick": start_playing}, "Start"),
37-
)
31+
return idom.html.div(idom.html.h1("You lost"), start_button)
3832
else:
39-
return idom.html.div(
40-
idom.html.h1("Click to play"),
41-
idom.html.button({"onClick": start_playing}, "Start"),
42-
)
33+
return idom.html.div(idom.html.h1("Click to play"), start_button)
4334

4435

4536
class Direction(enum.Enum):
@@ -50,7 +41,7 @@ class Direction(enum.Enum):
5041

5142

5243
@idom.element
53-
async def GameLoop(grid_size, block_scale, set_game_state):
44+
def GameLoop(grid_size, block_scale, set_game_state):
5445
# we `use_ref` here to capture the latest direction press without any delay
5546
direction = idom.hooks.use_ref(Direction.ArrowRight.value)
5647

@@ -75,17 +66,23 @@ async def on_direction_change(event):
7566
for location in snake:
7667
assign_grid_block_color(grid, location, "white")
7768

69+
new_game_state = None
7870
if snake[-1] in snake[:-1]:
7971
assign_grid_block_color(grid, snake[-1], "red")
80-
set_game_state(GameState.lost)
72+
new_game_state = GameState.lost
8173
elif len(snake) == grid_size ** 2:
8274
assign_grid_block_color(grid, snake[-1], "yellow")
83-
set_game_state(GameState.won)
75+
new_game_state = GameState.won
8476

8577
interval = use_interval(0.5)
8678

87-
@use_async_effect
79+
@idom.hooks.use_async
8880
async def animate():
81+
if new_game_state is not None:
82+
await asyncio.sleep(1)
83+
set_game_state(new_game_state)
84+
return
85+
8986
await interval
9087

9188
new_snake_head = (
@@ -105,31 +102,6 @@ async def animate():
105102
return grid
106103

107104

108-
def use_async_effect(function):
109-
def ensure_effect_future():
110-
future = asyncio.ensure_future(function())
111-
112-
def cleanup():
113-
if future.done():
114-
future.result()
115-
else:
116-
future.cancel()
117-
118-
return cleanup
119-
120-
idom.hooks.use_effect(ensure_effect_future)
121-
122-
123-
def use_interval(rate):
124-
usage_time = idom.hooks.use_ref(time.time())
125-
126-
async def interval():
127-
await asyncio.sleep(rate - (time.time() - usage_time.current))
128-
usage_time.current = time.time()
129-
130-
return asyncio.ensure_future(interval())
131-
132-
133105
def use_snake_food(grid_size, current_snake):
134106
grid_points = {(x, y) for x in range(grid_size) for y in range(grid_size)}
135107
points_not_in_snake = grid_points.difference(current_snake)
@@ -142,6 +114,16 @@ def set_food():
142114
return food, set_food
143115

144116

117+
def use_interval(rate: float) -> Awaitable[None]:
118+
usage_time = use_ref(time.time())
119+
120+
async def interval() -> None:
121+
await asyncio.sleep(rate - (time.time() - usage_time.current))
122+
usage_time.current = time.time()
123+
124+
return asyncio.ensure_future(interval())
125+
126+
145127
def create_grid(grid_size, block_scale):
146128
return idom.html.div(
147129
{

docs/source/examples/todo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
@idom.element
5-
async def Todo():
5+
def Todo():
66
items, set_items = idom.hooks.use_state([])
77

88
async def add_new_task(event):

docs/source/examples/use_reducer_counter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def reducer(count, action):
1313

1414

1515
@idom.element
16-
async def Counter(initial_count):
16+
def Counter(initial_count):
1717
count, dispatch = idom.hooks.use_reducer(reducer, initial_count)
1818
return idom.html.div(
1919
f"Count: {count}",

docs/source/examples/use_state_counter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def decrement(last_count):
1010

1111

1212
@idom.element
13-
async def Counter(initial_count):
13+
def Counter(initial_count):
1414
count, set_count = idom.hooks.use_state(initial_count)
1515
return idom.html.div(
1616
f"Count: {count}",

docs/source/getting-started.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Let's look at the example that you may have seen
1010
1111
1212
@idom.element
13-
async def Slideshow():
13+
def Slideshow():
1414
index, set_index = idom.hooks.use_state(0)
1515
1616
def next_image(event):
@@ -34,7 +34,7 @@ Since it's likely a lot to take in at once, we'll break it down piece by piece:
3434
.. code-block::
3535
3636
@idom.element
37-
async def Slideshow():
37+
def Slideshow():
3838
3939
The ``idom.element`` decorator creates an :ref:`Element <Stateful Elements>` constructor
4040
whose render function is defined by the `asynchronous function`_ below it. To create

docs/source/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ Libraries for defining and controlling interactive webpages with Python
2525
Try it Now!
2626
-----------
2727

28-
- In a Jupyter Notebook - |launch-binder|
28+
- Using working :ref:`Examples`
2929

30-
- Using working :ref:`examples <Examples>`
30+
- In a Jupyter Notebook - |launch-binder|
3131

3232

3333
Early Days
@@ -48,7 +48,7 @@ user clicks an image:
4848
import idom
4949
5050
@idom.element
51-
async def Slideshow():
51+
def Slideshow():
5252
index, set_index = idom.hooks.use_state(0)
5353
5454
def next_image(event):

docs/source/life-cycle-hooks.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ use_effect
106106
The ``use_effect`` hook accepts a function which may be imperative, or mutate state. The
107107
function will be called immediately after the layout has fully updated.
108108

109-
Mutations, subscriptions, delayed actions, and other `side effects`_ can cause
109+
Asynchronous actions, mutations, subscriptions, and other `side effects`_ can cause
110110
unexpected bugs if placed in the main body of an element's render function. Thus the
111111
``use_effect`` hook provides a way to safely escape the purely functional world of
112112
element render functions.
@@ -272,6 +272,18 @@ hook alongside :ref:`use_effect` or in response to element event handlers.
272272
:ref:`The Game Snake` provides a good use case for ``use_ref``.
273273

274274

275+
**Unique Hooks**
276+
----------------
277+
278+
Hooks which are specific to IDOM and not present in React.
279+
280+
281+
use_async
282+
---------
283+
284+
...
285+
286+
275287
**Rules of Hooks**
276288
------------------
277289

0 commit comments

Comments
 (0)