Use Callback
Overview¶
use_callback is a ReactPy hook that caches a function, replacing it only when one of its listed dependencies changes.
cached_handler = use_callback(handler, dependencies=[dep_1, dep_2])
It is the function-shaped counterpart of use_memo: same rules, same dependency behaviour, but the thing you cache is a callable rather than a value.
You probably do not need it
In React, useCallback earns its place by keeping a child from re-rendering behind memo and by keeping effect dependencies from thrashing. Neither reason applies to ReactPy. There is no render bailout and no memo, and a function you define in the component body already counts as an unchanged dependency, because ReactPy compares local functions by their code rather than by identity. So the effect-churn problem useCallback exists to solve does not occur here.
What it does give you is a genuinely stable function object, which matters only when the function leaves ReactPy and something else keys on its identity — a subscription registry, an event bus, a cached table. If none of that describes your code, write a plain def and skip this page.
Reference¶
use_callback(function, dependencies)¶
Call use_callback at the top level of your component to cache an event handler or other callback.
from reactpy import use_callback
Parameters¶
function: The function you want to keep. It must be pure in the sense that matters here: same closure, same behaviour.-
dependencies: The values the function reads. ReactPy replaces the cached function when any of them changes. Three shapes, identical touse_memo:You pass Meaning dependencies=[a, b]Replace the function when aorbchanges.omitted (the default) Infer the dependencies from the function's closure. dependencies=NoneReturn a new function on every render — no caching at all.
Returns¶
The cached function — either the one from a previous render, or, if a dependency changed, the one you just passed in.
Caveats¶
- The function you pass is not discarded, it is stored. ReactPy does not call it; it keeps the object. That is why caching a function that closes over state can hand you an old closure — see A cached handler sees stale values.
dependencies=[]pins the function forever. The same object comes back on every render, including the one holding the values it closed over on the first render. This is the single most commonuse_callbackbug, and ReactPy will not warn you.- Listing a dependency you read is not optional. Forgetting one produces a handler that quietly acts on outdated values. Nothing checks this for you.
- Stable identity does not mean stable behaviour. A function can be the same object across renders and still be wrong, because identity was the point and the closure went stale. Cache identity when something external needs identity; do not use it as a performance gesture.
- Comparison follows
strictly_equal, not identity — see the equivalent caveat inuse_memo: same type first, code comparison for local functions,==otherwise. - The decorated name is the function, not a wrapper.
@use_callback(dependencies=[x])receives your function and returns a function, so the name still calls fine — unlikeuse_memo, where the name becomes the value. - Like every hook, call it only at the top level — never in a loop, a conditional, or after an early
return.
Usage¶
When a stable identity matters¶
Here is the case use_callback is actually for. The component subscribes to a bus that lives outside ReactPy. That bus stores handlers in a list and skips duplicates by identity — so a new function object on each render registers another handler, and every handler receives each message.
Press Re-render three times. handlers registered stays at 1, because the handler really is the same object each time. Press Publish and it is delivered once. Replace the use_callback line with a plain def and the same interaction registers four handlers.
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 29 30 31 32 | |
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 | |
1 | |
Note the heard updater: set_heard(lambda previous: previous + 1). It reads no state, which is what lets this handler keep an empty dependency list honestly — an updater function gets the current value at the moment the setter runs, so the handler does not need to close over anything.
A cached handler sees stale values¶
The notes app below caches a handler with dependencies=[] while the handler reads draft. ReactPy keeps the first function, which closed over an empty draft, so every click adds a blank note no matter what you type.
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 29 30 31 32 33 | |
1 | |
Type something and press Add note: the list gains (blank). The handler is working perfectly — on the render it was created on.
Listing what the handler reads¶
The fix is to name the values the function reads. Now the cached function is replaced whenever notes_list or draft changes, so a click always sees what is currently in the box — while the identity still stays put between 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 29 30 31 32 | |
1 | |
Type something and press Add note: the text lands. These two files differ by one line; the behaviour difference is the whole point.
Omitting the dependency list¶
Leave out dependencies and ReactPy infers them from the function's closure, exactly as it does for use_memo and use_effect. For a handler that reads component state this is usually what you want, and it removes the "forgot a dependency" class of bug. Omitting is not the same as dependencies=[] — the inferred list is built from what the function actually reads, which is rarely empty.
Troubleshooting¶
My handler acts on old values¶
You cached it with an empty or incomplete dependency list. The function is the same object, and so is everything it closed over. Either list the values it reads, or stop the value from being read at all by using an updater — set_items(lambda previous: [*previous, new]) instead of set_items([*items, new]).
My handler never fires¶
Check the attribute name. {"on_click": handler} renders without complaint, lands in eventHandlers, and is never called — the client registers a DOM listener for the key with on stripped, so the underscore spelling subscribes to an event named _click that no browser ever fires. The key must be the DOM event name in camelCase ("onClick"), and the value must be your function, not a call to it. A wrong name is invisible: the only difference is that nothing happens.
I still get a new function every render¶
- You passed
dependencies=None, which means exactly that. - A dependency is rebuilt each render and does not compare equal — a plain class instance without
__eq__, for instance. - The function is defined outside the component and you expect it to be cached;
use_callbackcaches what you hand it, per component instance.
Do I need this to stop a child re-rendering?¶
No — that mechanism does not exist in ReactPy. See the note at the top of the page: children re-render whenever their parent does, regardless of prop or handler identity.