Use Async Effect
Overview¶
use_async_effect is a ReactPy hook that lets you run an async def effect, and keep it running across renders.
use_async_effect(effect)
use_async_effect(effect, dependencies=[value])
Reference¶
use_async_effect(effect, dependencies=..., shield=False)¶
Call use_async_effect at the top level of your component to run an asynchronous effect. ReactPy wraps the coroutine in an asyncio.Task, so it runs on the connection's event loop rather than blocking the render.
from reactpy import use_async_effect
Parameters¶
-
effect: Anasync deffunction taking no arguments. To run work in the background and stop it later, loop and handle cancellation — see running a long-lived effect. -
dependencies(optional): Same four-way meaning as inuse_effect, and the default is again not React's:Value Behaviour (verified) omitted ( ...)Inferred from the free variables of your closure. dependencies=[]Starts once, on mount. dependencies=[a, b]Restarts when the identity of aorbchanges.dependencies=NoneRestarts after every render. -
shield(optional): WhenTrue, an in-flight effect is not cancelled when the component unmounts or the effect is superseded. DefaultFalse.
Returns¶
None when given the effect, or a decorator when called without one.
Caveats¶
- The effect must take zero arguments. ReactPy calls
asyncio.create_task(func())(core/hooks.py). The 1.x signatureasync def effect(stop):is therefore broken — and it fails silently. Verified: the component renders normally, the model carries no error, the browser shows nothing wrong; the only trace isError in effect/TypeError: ... missing 1 required positional argument: 'stop'in the server log. If your async effect "does nothing", check the server log first. - There is no
stopevent to wait on. Cancellation is ordinaryasyncio.CancelledError; a long-running effect should loop and break out of it. - Cancelling is also how clean-up happens: when dependencies change, the running task is cancelled before the new one starts.
- The default dependency behaviour is inference, not "every render". See
use_effectfor the full table. - Effects run on the server.
documentandwindowdo not exist. - An exception raised inside the coroutine is logged on the server only — no error reaches the model or the browser.
- A new effect does not wait for the old one to finish unless you await cancellation, so two overlapping tasks can briefly run side by side. Use
shielddeliberately, not by default.
Usage¶
Importing use_async_effect¶
from reactpy import use_async_effect
Running a one-shot async effect¶
For a single await — a request, a slow lookup — write the coroutine straight:
@component
def stats(repo):
data, set_data = use_state(None)
async def effect():
result = await fetch_stats(repo)
set_data(result)
use_async_effect(effect, dependencies=[repo])
return html.p(data if data is not None else "loading...")
Setting state from the effect schedules the re-render; nothing else is needed.
Running a long-lived effect¶
To keep work running across renders, loop and honour cancellation. This is what ReactPy's version of React's setInterval example looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
1 | |
The clock advances on its own — verified, one update per second. When dependencies change or the component unmounts, ReactPy cancels the task and the loop exits through CancelledError.
Do not wait for a stop event
ReactPy 1.x passed a stop event into async effects. That parameter no longer exists, and passing it is not reported anywhere the user can see. Write this:
async def effect():
while True:
do_work()
try:
await asyncio.sleep(1)
except asyncio.CancelledError:
break
Not this:
async def effect(stop): # 🚩 silent failure
while not stop.is_set():
...
Shielding an effect from cancellation¶
shield=True lets an in-flight effect run to completion even as the component unmounts — useful for finishing a request whose result must not be discarded:
use_async_effect(effect, dependencies=[], shield=True)
Verified: with shield=True a sleeping effect reached its completion line after the layout exited; with the default it was cancelled at that same moment. Leave this off unless you specifically want work to outlive the component, since it also means the effect keeps holding references and consuming the loop.
Cleanup¶
Async effects do not return a clean-up function; cancellation is the clean-up. Put teardown in the except asyncio.CancelledError branch, and re-raise so asyncio can finish cancelling:
async def effect():
handle = open_connection()
try:
while True:
await handle.poll()
except asyncio.CancelledError:
handle.close()
raise
Re-raising matters: swallowing CancelledError leaves the task in a state asyncio did not expect.
Choosing between use_effect and use_async_effect¶
| Need | Use |
|---|---|
| A synchronous side effect | use_effect |
Anything await-based | use_async_effect |
| A repeating timer, polling, a subscription stream | use_async_effect with a loop |
| Work that must block the render | neither — do it during render, or raise |
Passing an async def to use_effect fails loudly, which is the one mismatch ReactPy does report: TypeError: use_effect does not support async functions. Use use_async_effect instead.