Use Memo
Overview¶
use_memo is a ReactPy hook that caches the result of a calculation, recomputing it only when one of its listed dependencies changes.
cached_value = use_memo(calculate_value, dependencies=[dep_1, dep_2])
Use it when a calculation is genuinely expensive and the inputs it depends on change less often than the component renders. It caches a value; it does not stop anything from rendering.
What use_memo does not do in ReactPy
It does not prevent a child component from re-rendering. ReactPy has no render bailout and no memo: a child's body runs whenever its parent re-renders, even with identical props, and even if you cache the child element itself with use_memo. Texts ported from React that use useMemo to "skip re-rendering of components" describe a mechanism that is not here. use_memo saves the computation, nothing else.
Reference¶
use_memo(function, dependencies)¶
Call use_memo at the top level of your component to cache a calculation.
from reactpy import use_memo
Parameters¶
function: A function taking no arguments that returns the value you want. ReactPy calls it only when it must. It must be pure: same inputs, same output, no side effects.-
dependencies: The values the calculation reads. ReactPy recomputes when any of them changes. It accepts three shapes:You pass Meaning dependencies=[a, b]Recompute when aorbchanges.omitted (the default) ReactPy reads the function's closure and infers the free variables. dependencies=NoneRecompute on every render.
Returns¶
The current value — either freshly computed, or the one cached from the last render in which a dependency changed.
Caveats¶
- The decorated name holds the value, not a function. The decorator form is the call form under another name, and the call form returns the value -- so after decorating,
my_valueis the cached result, and calling it raisesTypeError: 'int' object is not callable. To keep the name callable, give the inner function its own name and pass it touse_memo. - Caching lasts for the lifetime of the component. A cached value survives as long as the component stays mounted; there is no way to invalidate it from inside. Mounting the component under a new
"key"throws it away, because that unmounts and remounts the component and every hook starts over. - You must list every value the function reads. ReactPy does not check, and does not warn. A missing dependency produces a silently stale value, not an error. See I removed a dependency and nothing complained.
- Comparison is not plain identity. Dependencies go through
reactpy.core.hooks.strictly_equal, which requires the two values to be the same type first, compares two locally-defined functions or lambdas by their code objects (so a fresh, structurally identical lambda counts as unchanged), and otherwise compares with==, falling back toisonly if that raises. Two practical consequences: a freshly builtlistordictwith equal contents does not force a recompute, and a value that changes type between renders —1then1.0, or0thenFalse— always counts as changed. - Mutating a dependency in place defeats the cache. Change an object and pass the same object back, and it compares equal to itself, so the cached value is kept. Build a new object instead — see Updating Objects in State.
- A ref never invalidates a memo. Writing to
some_ref.currentdoes not change how the ref compares, so listing ause_refbox independenciesaccomplishes nothing. - Like every hook, call it only at the top level — never in a loop, a conditional, or after an early
return. ReactPy does not check; a hook that runs on some renders but not others produces a scrambled component and a crypticIndexError.
Usage¶
Skipping an expensive calculation¶
Give use_memo the function that does the work, and list what it reads. The leaderboard sorts seven rows here, which is nothing — but it is the same shape you would use for a sort over ten thousand, and the counter on screen shows exactly how often the sort runs.
Press Change highlight: the component re-renders, the sort does not run, and sorted 1 time(s) stays put. Press Everyone scores +1: players changes, so the sort runs again.
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
1 | |
The counter (sorts) and both buttons exist only to make the caching observable. In real code you would keep the use_memo call and drop the rest.
How expensive is "expensive"?¶
Enough to measure. Sorting a small list, building a dict from a handful of items, or formatting a string is not worth it — the hook has a cost of its own, and a dependency list you must keep correct is a cost too.
Reach for it when you recognize one of these:
- A sort, filter, or search over a large collection.
- Parsing: JSON, CSV, a template, a source file.
- Building a structure that then gets walked repeatedly.
- Anything you have already timed and found annoying.
If you have not measured it, prefer the plain calculation. Adding use_memo later is a one-line change precisely because the calculation is already in its own function.
Should you add use_memo everywhere?¶
No. Each one is a dependency list you now have to maintain correctly, and a wrong list is a silent bug rather than a loud one. Memoize where you have measured, not everywhere.
Omitting the dependency list¶
Leave out dependencies and ReactPy infers them from the free variables in the function's closure. This is a ReactPy feature with no React equivalent, and it removes the most common class of mistake, because a value the function reads is by definition a free variable.
The word count below reads text but not upper, so toggling the case re-renders the component without re-running the count. Watch counted 1 time(s).
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 | |
1 | |
Two catches. Inference only sees closure variables, so a module-level name, or anything reached through an object, is invisible to it. And inference is a convenience for a function defined inside the component — pass the list explicitly when the function comes from elsewhere.
Recomputing on every render¶
dependencies=None opts out of caching entirely: the function runs on every render. That is what you want when the calculation reads something no dependency list can express, such as a module-level mutable.
The label below reads config, which lives at module scope. Inferred dependencies would be empty, so the label would be computed once and never notice a changed config. With dependencies=None, label() runs each render.
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 | |
Troubleshooting¶
My memo value never updates¶
In order of likelihood:
- You mutated a dependency instead of replacing it. Same object in, equal to itself, cached value kept. Build a new object.
- You listed a ref as a dependency. Mutating
some_ref.currentnever invalidates anything. Use state, or list the value itself. - The dependency compares equal to its replacement. Two
[1, 2, 3]lists are equal, so rebuilding one each render changes nothing. - You left the value out of the list. See the next item.
I removed a dependency and nothing complained¶
Because nothing checks. The example below omits tax_rate from the list, and it seems to work: press Add 10% tax twice and the rate climbs while the total stays frozen at whatever a 0% rate produced. Then press Add an item — a real dependency — and the total jumps to 12.00, silently incorporating both tax increases you could see never happened.
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 | |
This is the failure mode to watch for: a stale value that eventually arrives, so it reads as lag rather than as a bug. List everything the function reads, or omit dependencies and let ReactPy infer them.
My calculation runs on every render anyway¶
- You passed
dependencies=None. - A dependency really is new each render — commonly an object constructed in the component body above the
use_memocall. - A dependency is an object whose
==is identity-based and which you rebuild each time.
I wanted to stop a child from re-rendering¶
use_memo cannot do this in ReactPy, and caching the child element does not help either — a cached element is still re-rendered. ReactPy re-renders children unconditionally. If a child is genuinely expensive, the levers are to make its own calculations cheap (memoize inside it) or to avoid rendering it at all behind a conditional.