Use Effect
Overview¶
use_effect is a ReactPy hook that lets you synchronize a component with an external system.
use_effect(effect)
use_effect(effect, dependencies=[value])
Reference¶
use_effect(effect, dependencies=...)¶
Call use_effect at the top level of your component to run an effect after rendering.
from reactpy import use_effect
Parameters¶
-
effect: A function taking no arguments. ReactPy calls it after the component renders. It may return a clean-up function (also taking no arguments), which ReactPy runs before the next time this effect runs and again when the component unmounts.The effect must be pure, and it must not be declared
async def— ReactPy raises for that; useuse_async_effectinstead. -
dependencies(optional): Controls when the effect re-runs. Unlike React, this argument has four distinct settings, and the default is not React's:Value Behaviour (verified) omitted ( ...)Inferred. ReactPy reads the free variables of your closure ( core/hooks.py,_try_to_infer_closure_values) and re-runs when any of them changes. A closure that reads nothing runs once.dependencies=[]Runs once, after mount. dependencies=[a, b]Runs when aorbchanges.dependencies=NoneRuns after every render. "Changes" means
strictly_equal(core/hooks.py), notis: the two values must share a type, two locally-defined functions or lambdas are compared by bytecode, and everything else falls through to==withisas the last resort. A rebuilt list or dict with equal contents does not re-run the effect. Use Memo documents the same function in full.
Returns¶
None when given the effect, or a decorator when called without one.
Caveats¶
- The default is inference, not "every render". In React, omitting the dependency array means the effect runs after every render. In ReactPy it means "re-run when the variables my closure reads change". Code ported from React will therefore behave differently — pass
dependencies=Noneif you really want every-render behaviour, though usually you want to be explicit instead. - Inference is structural, not declared, and it does not compare by
id. A closure that happens to read an extra local variable gains an extra dependency, and the comparison isstrictly_equal— type, then bytecode for local functions, then==— so a rebuilt-but-equal container counts as unchanged. If the effect runs more or less often than you expect, look at which names it actually reads, and then at how those values compare. - Effects run on the server, after rendering, not in the browser. There is no DOM to touch:
documentandwindowdo not exist here. There is nodocumentorwindowto reach for, and no ReactPy equivalent of attaching a listener to a node — see Responding to Events for the supported way to react to the browser. - Children's effects run before their parent's, and siblings run in reverse order. Verified output:
['child effect b', 'child effect a', 'parent effect']. Two effects in one component run in declaration order. Do not rely on React's parent-first or source-order guarantees. - Passing
async deftouse_effectfails loudly:TypeError: use_effect does not support async functions. Use use_async_effect instead.The error lands in the model'serrorfield and the component stops rendering. - An exception raised inside an effect body is logged on the server only; the model carries no error and the UI looks unchanged. Check the server log.
- Effects are not a substitute for computing values. Derive them during render or with
use_memo. - There is no
use_layout_effect,use_insertion_effect, oruse_imperative_handle. Nothing runs synchronously during the render phase.
Usage¶
Importing use_effect¶
from reactpy import use_effect
Describing an effect¶
Pass the setup function directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
1 | |
Or use it as a decorator, which is equivalent:
@use_effect
def effect():
print("mounted")
Telling ReactPy when to re-run the effect¶
Pass a dependency list to re-run only when something relevant changes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
1 | |
Switching topic tears down the old subscription before creating the new one — verified, the subscriber count stays at 1 across switches and drops to 0 on unmount.
Omitting the dependencies argument¶
use_effect(effect) # inferred from what the closure reads
The example below makes inference visible. The effect reads count but not label, so renaming re-renders the component without re-running the effect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
1 | |
Pressing Rename twice adds no new log line — verified. Be aware this works because of which names the closure reads; it is implicit, so many teams prefer to spell the list out.
Passing an empty dependency array¶
use_effect(effect, dependencies=[]) # once, after mount
For mount-only work — subscribing, starting a timer, reading a config — say so explicitly.
Passing None¶
use_effect(effect, dependencies=None) # after every render
This is the closest ReactPy gets to React's "no array" behaviour. It re-runs the effect after every render, verified. Reach for it rarely: it usually means the effect is doing work that belongs in render or in use_memo.
Adding clean-up¶
Return a function from the effect. ReactPy calls it before the next run of that effect and when the component unmounts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 | |
Connecting and disconnecting runs the pair in order — verified: connected, disconnected, connected across connect / disconnect / reconnect.
Clean-up is the difference between a leak and a working app
Because ReactPy components live in long-lived server processes, a forgotten subscription is not a brief flash of stale data — it is a permanent leak that outlives every browser tab that opened it. Every subscribe, every timer, every held connection needs a clean-up function.
Effects and the request lifecycle¶
An effect runs in the connection's own task, after the render that scheduled it. It can call setters, and those schedule further renders. Two consequences:
- An effect that awaits gives the event loop control. Long
awaits belong inuse_async_effect, which is built for it. - Setting state from an effect that depends on that same state loops forever. If an effect seems to spin, read what it sets and what it depends on.
Reading the latest state inside an effect¶
An effect closes over the render that created it. If it then runs for a long time — an interval callback, a subscription handler — it can act on values that are no longer current. Two ways to handle it:
- Put the value in the dependency list, so a change tears the effect down and starts a new one with fresh values.
- Keep the value in a
use_refbox and readref.currentwhen you need it. The box persists across renders, so readers see the newest value — as long as the write happens somewhere legal. Do not assignref.currentfrom the component body: a render-phase write is exactly the impurity Use Ref warns about, and ReactPy has no Strict Mode to catch it. Assign it where the value is already produced — the event handler that changed it — or in its own effect, which runs after the render.
If an effect reads state it does not declare, that is usually a sign the effect should be re-created when that state changes — reach for the dependency list first.