Use State
Overview¶
use_state is a ReactPy hook that lets you add a state variable to your component.
value, set_value = use_state(initial_value)
Reference¶
use_state(initial_value)¶
Call use_state at the top level of your component to declare a state variable.
from reactpy import use_state
Parameters¶
initial_value: The value you want the state to start with. It can be any Python value. Alternatively, pass a function taking no arguments and returning the initial value; ReactPy calls it on the first render only, which avoids rebuilding an expensive value on every render. See avoiding recreating the initial state.
Returns¶
use_state returns a reactpy.types.State — a named tuple of two items, which is normally unpacked:
value: The current state, as of the start of this render.set_value: A function that updates the state and schedules a re-render. Seeset_value.
Because State is a named tuple you can also reach the halves explicitly (state.value, state.set_value), but unpacking is the convention.
Caveats¶
use_stateis a hook, so call it only at the top level of your component or of your own hooks — never in a loop, a conditional, or after an earlyreturn. ReactPy does not check for this: changing which hooks run between renders produces a scrambled component and a crypticIndexError: list index out of rangerather than a helpful message. If you need conditional state, extract a component.initial_valueis used only on the first render. Later changes to the expression are ignored — see resetting state with a key.- Do not pass a function you intend to store. A callable
initial_valueis treated as an initializer and called.use_state(lambda value: value * 2)does not store the lambda; ReactPy calls it with no arguments and the component fails. To keep a function in state, wrap it (use_state(lambda: my_function)) or store it in ause_ref. - Unlike React, ReactPy has no Strict Mode, so your initializer is called exactly once — verified: one call across four renders. Nothing will run it twice to expose impurity for you, so test purity yourself if it matters.
set_value(next_value)¶
The set_value function returned by use_state updates the state and schedules a re-render.
Parameters¶
next_value: The value you want the state to become. Any Python value, or a function taking one argument and returning the next state. That function receives the current value, runs eagerly inside the setter, and must be pure. See updating state based on the previous state.
Returns¶
None.
Caveats¶
- Setting state does not change the variable you already have in this render — it schedules a new one. See State as a Snapshot.
- Setting state to a value equal to the current one does not re-render. The comparison is
core/hooks.py,strictly_equal— and it is not an identity check: the two values must share a type, two locally-defined functions or lambdas are compared by their code, and everything else is compared with==, falling back to identity only if that raises. So a brand-new object is dropped too whenever it merely compares equal: setting[1, 2, 3]while the state is an equal list renders nothing, and the same goes for any class whose__eq__matches. Only values with identity-based__eq__— plain class instances, say — re-render when you replace them. set_valuereplaces the state; it never merges a partial object into it. Verified: setting{"a": 9}over{"a": 1, "b": 2}yields{"a": 9}. Update objects withset_value({**value, "k": v})— see Updating Objects in State.- Mutating the current value and passing it back does nothing. The comparison finds the value equal to itself, so no render is scheduled — and there is no error, no warning, and nothing in the console.
set_value(items.append(x))also storesNone, because most mutating list methods returnNone. - If the updater function raises, the error is logged on the server as
Failed to execute event handlerand swallowed; the state is left unassigned and the UI simply stops updating. Check the server log, not the browser console. set_valueis a stable object: the same callable is returned on every render of that component. Verified across four renders. That makes it safe to put in a dependency list or pass to a child without causing extra renders.
Usage¶
Adding state to a component¶
Call use_state at the top level of your component, and use the returned value while rendering:
1 2 3 4 5 6 7 8 9 10 11 | |
1 | |
Each state variable gets its own setter, so prefer several narrow variables over one crowded object:
first_name, set_first_name = use_state("Ada")
last_name, set_last_name = use_state("Lovelace")
age, set_age = use_state(42)
Counter (number)¶
To update a number based on its current value, use a function:
def handle_add(event):
set_step(lambda step: step + 1)
Or read the value from this render directly when you don't need the latest: set_step(step + 1).
Text field (string)¶
A controlled input reads its "value" from state and writes back through "onChange":
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 | |
Type into the field and the heading follows. Note the key names: "value" and "onChange" are DOM attribute names inside the attributes dictionary, while the Python names around them are snake_case.
There is no e.target.name, because the client does not send DOM attributes — bind extra context with a lambda instead. See Responding to Events.
Checkbox (boolean)¶
event["target"]["checked"] tells you whether the box is ticked:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
1 | |
Tick the box and the status line flips. The client deliberately re-reads value, checked, files, type, and name from the element, which is why they arrive even though most element properties are stripped to keep payloads small.
Form (two variables)¶
Two inputs, two state variables, no interference between them:
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 | |
Updating state based on the previous state¶
A common case is updating state based on the value that came before. Pass a function instead of a value:
set_count(lambda count: count + 1)
ReactPy calls your function with the current state and assigns the result.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 | |
Click "+3" and the counter reaches 3, because each updater sees what the previous one produced — verified. Had these been set_count(count + 1) instead, all three would compute from the same render-time count and the result would be 1. See Queueing a Series of State Updates.
Is using an updater always preferred?¶
No. If you are setting state to a value that does not depend on previous state, pass the value. Updaters are for when the next value is derived from the last, or when several updates to the same variable must compose within one handler.
Passing the updater function¶
The function must be pure — it should only compute and return the next value. Do not send requests, mutate the argument, or set other state inside it.
# Correct: pure, returns the next value
set_count(lambda count: count + 1)
# Wrong: mutates, and returns None
set_items(lambda items: items.append("new"))
Passing the next state directly¶
When the value comes from outside, pass it plainly:
set_first_name("Ada")
Updating objects and arrays in state¶
Pass a new object; there is no merge. For dicts use unpacking, for lists use a comprehension or slicing:
set_person({**person, "firstName": event["target"]["value"]})
set_items([*items, {"id": next_id, "text": text}])
Full treatment, including nested updates and the silent-drop failure mode, is on Updating Objects in State and Updating Arrays in State.
Avoiding recreating the initial state¶
ReactPy ignores initial_value after the first render, but the expression still runs on every render. If building it is expensive, pass a function:
1 2 3 4 5 6 7 8 | |
1 | |
create_expensive_state is called once, on mount — verified (one call across four renders).
Passing the initializer function¶
state, set_state = use_state(create_expensive_state) # called once
Passing the initial state directly¶
state, set_state = use_state(create_expensive_state()) # built every render
Correct, but wasteful. Note again that a bare function is interpreted as an initializer, which is why the second form calls it explicitly.
Resetting state with a key¶
ReactPy normally preserves state between re-renders, matching children by position and key. To force a component to start fresh, give its call site a different key:
1 2 3 4 5 6 7 8 9 10 11 | |
1 | |
Switching accounts resets the counter to zero, because a changed key means a different component as far as ReactPy is concerned. Verified: a keyed child bumped to 2 came back at 0 after its key changed, while the identical child rendered without a key kept its count.
Storing information from previous renders¶
To keep a history, read the previous list through an updater so it composes correctly, and capture this render's value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
1 | |
Clicking twice records Previous values: 0, 1 — verified. The history name in the closure would still be the render-time value, which is exactly why the updater form matters here. For genuinely mutable, non-rendering values, use use_ref instead.