Use Ref
Overview¶
use_ref is a ReactPy hook that gives you a mutable box which survives re-renders. Writing to it never causes a render.
value_box = use_ref(initial_value)
A ref is for values your component uses but never renders. It is a plain Python object holding one field, so reading and writing it is just attribute access — no setter, no scheduling, no re-render.
There is no DOM ref in ReactPy
React's useRef is often used to grab a DOM node — inputRef.current.focus(), videoRef.current.play(), myDiv.current.scrollIntoView(). That use does not exist in ReactPy. Your components run on the server, where there is no DOM, and ReactPy has no mechanism for handing a browser node to Python. There is no ref_current attribute, no callback ref, nothing to attach a node to. Anything you write as a "ref_..." attribute is an unknown attribute the client silently ignores.
Express browser behavior through state and ordinary attributes instead: {"autoFocus": True}, {"value": text}, {"disabled": not ready}. If you need genuinely imperative browser code, that belongs in a client-side app, where your Python runs in the browser.
Reference¶
use_ref(initial_value)¶
Call use_ref at the top level of your component to declare a value box.
from reactpy import use_ref
Parameters¶
initial_value: The value the box starts with. It can be any Python value, including a mutable one.
Returns¶
A reactpy.utils.Ref object — the same object on every render of that component. It has two members:
current: The value. Read and write it directly:value_box.current = new_value. Reading it does not schedule a render.set_current(new_value): Assignscurrentand returns the value it replaced. Because it is a call rather than an assignment, it fits where a statement cannot go — inside alambda, for example.
Caveats¶
-
initial_valueis evaluated on every render.use_ref(build_something_expensive())callsbuild_something_expensive()each time the component renders, then throws all but the first result away. Verified: four renders, four calls. To build something once, start empty and fill it in:index = use_ref(None) if index.current is None: index.current = build_something_expensive() -
Writing to a ref never triggers a render. That is the point, and also the trap: if the value is something the screen shows, it will not update. See Ref versus state.
- Mutating a ref does not invalidate anything. Dependency comparison treats the box as unchanged no matter what you write into it, because it compares equal to itself. Listing a ref in a
dependencieslist is therefore useless — the effect or memo will never re-run because of it. Verified: an effect withdependencies=[box]ran once while itsbox.currentwas written to three times. - A ref is unhashable.
hash(use_ref(0))raisesTypeError, so it cannot be adictkey or a set member. Its==compares contents: two separateRef(0)boxes are equal. - Render
value_box.current, not the box.html.p(my_ref)does not raise; it renders the repr, so the reader seesRef(0)on the page. Passing a ref as an attribute value fails at serialization instead. - 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: shuffling which hooks run between renders yields a scrambled component and a crypticIndexError, not a helpful message. - ReactPy has no Strict Mode, so nothing runs your component twice to catch impure code. A ref written to during rendering is exactly as unsafe here as in React, and nothing will warn you. See Keeping Components Pure.
Usage¶
Ref versus state¶
The question is not "which is cheaper" but who needs to know. If changing the value must update the screen, it is state. If only the code needs it, it is a ref.
The two counters below start together and then drift apart, because only one of them causes a render. The ref counter's new value sits in the box unseen until something else renders the component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
1 | |
The usual split:
| You need... | Use |
|---|---|
| A value the UI renders | use_state |
| A value only handlers or effects read | use_ref |
| A value that drives the UI and is written by a long-lived loop | Both: state for the UI, ref for the loop |
| Something built once, then read-only | use_ref, with the guard |
| A mutable value the UI renders | use_state — replace the object instead of mutating it; see Updating Objects in State |
Remembering a value built once¶
Because the box is the same object across renders, it is the natural place for something expensive to construct and cheap to read: a parsed file, an index, a client object. Start it empty so the expensive call is not repeated on every render.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
1 | |
If the value should be rebuilt when some input changes, a ref is the wrong tool — that is what a dependencies list is for. See use_memo.
Sharing a value with a long-lived effect¶
An use_async_effect loop keeps running across renders, so it closes over the state values from the render that started it. A ref sidesteps this: it is the same object every render, so the loop reads whatever the handlers wrote after it started.
The stopwatch below keeps the start timestamp in a ref. The ticking effect reads it and the buttons write it, none of which needs a render of its own. The value the screen shows, elapsed, is state.
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 | |
1 | |
Writing from inside a lambda¶
box.current = value is an assignment, which is a statement, so it cannot be the body of a lambda. set_current exists for that case, and hands back what you replaced.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 | |
pick returns a lambda precisely so each button gets its own handler, and previous.set_current(theme) is what makes the one-line body possible.
Troubleshooting¶
My ref value is stale¶
Check which of these you did:
- You rendered the box, not its contents.
html.p(my_ref)printsRef(...). Usemy_ref.current. - You expected the write to re-render. A ref never schedules a render. If the screen must change, that value is state.
- You listed the ref as a dependency. Mutating
currentdoes not invalidate effects or memos — the box always compares equal to itself. Put the value in the dependency list, or use state. - You read a ref through an old closure. Inside a long-lived effect, re-read
box.currenton each pass instead of binding it to a local once.
My expensive setup runs on every render¶
You passed it as the argument: use_ref(build()) calls build() every render. Move it behind a guard:
box = use_ref(None)
if box.current is None:
box.current = build()
I need to focus an input, or scroll to an element¶
There is no way to do this from a ReactPy server component, and no "ref_current" attribute will add one. Use the declarative attributes the browser already understands — "autoFocus", "checked", "value", "disabled" — and reach for a client-side app if you truly need imperative browser access.