0
# Hooks and State Management
1
2
React-style hooks for managing component state, side effects, and context in functional components. Hooks enable stateful logic in ReactPy components and must be called from within component functions.
3
4
## Capabilities
5
6
### State Hook
7
8
The `use_state` hook manages component state with a getter and setter pattern.
9
10
```python { .api }
11
def use_state(initial_value: T | Callable[[], T]) -> tuple[T, Callable[[T], None]]: ...
12
```
13
14
**Parameters:**
15
- `initial_value`: Initial state value or a function that returns the initial value
16
17
**Returns:** Tuple of `(current_value, setter_function)`
18
19
**Usage Examples:**
20
21
```python
22
from reactpy import component, html, use_state
23
24
@component
25
def Counter():
26
count, set_count = use_state(0)
27
28
def increment():
29
set_count(count + 1)
30
31
def decrement():
32
set_count(count - 1)
33
34
return html.div(
35
html.h1(f"Count: {count}"),
36
html.button({"onClick": increment}, "Increment"),
37
html.button({"onClick": decrement}, "Decrement")
38
)
39
40
@component
41
def LazyInitialization():
42
# Use function for expensive initial computation
43
expensive_value, set_value = use_state(lambda: expensive_computation())
44
return html.div(f"Value: {expensive_value}")
45
```
46
47
### Effect Hook
48
49
The `use_effect` hook handles side effects with optional dependency tracking.
50
51
```python { .api }
52
def use_effect(function: Callable[[], None | Callable[[], None]], dependencies: list[Any] | None = None) -> None: ...
53
```
54
55
**Parameters:**
56
- `function`: Effect function, optionally returns cleanup function
57
- `dependencies`: List of values that trigger re-execution when changed (None = every render, [] = once)
58
59
**Usage Examples:**
60
61
```python
62
@component
63
def Timer():
64
seconds, set_seconds = use_state(0)
65
66
def setup_timer():
67
interval_id = set_interval(lambda: set_seconds(seconds + 1), 1000)
68
return lambda: clear_interval(interval_id) # Cleanup function
69
70
use_effect(setup_timer, []) # Run once on mount
71
72
return html.div(f"Timer: {seconds} seconds")
73
74
@component
75
def DocumentTitle(title: str):
76
def update_title():
77
document.title = title
78
79
use_effect(update_title, [title]) # Run when title changes
80
81
return html.h1(title)
82
```
83
84
### Callback Hook
85
86
The `use_callback` hook memoizes callback functions to prevent unnecessary re-renders.
87
88
```python { .api }
89
def use_callback(function: Callable[..., Any], dependencies: list[Any] | None = None) -> Callable[..., Any]: ...
90
```
91
92
**Parameters:**
93
- `function`: Function to memoize
94
- `dependencies`: List of values that invalidate the memoized callback
95
96
**Usage Examples:**
97
98
```python
99
@component
100
def TodoList():
101
todos, set_todos = use_state([])
102
103
add_todo = use_callback(
104
lambda text: set_todos([*todos, {"id": len(todos), "text": text}]),
105
[todos]
106
)
107
108
return html.div(
109
TodoForm({"onSubmit": add_todo}),
110
*[TodoItem(todo, key=todo["id"]) for todo in todos]
111
)
112
```
113
114
### Memo Hook
115
116
The `use_memo` hook memoizes expensive computations.
117
118
```python { .api }
119
def use_memo(function: Callable[[], T], dependencies: list[Any] | None = None) -> T: ...
120
```
121
122
**Parameters:**
123
- `function`: Function that computes the value
124
- `dependencies`: List of values that invalidate the memoized result
125
126
**Usage Examples:**
127
128
```python
129
@component
130
def ExpensiveCalculation(numbers: list[int]):
131
expensive_result = use_memo(
132
lambda: sum(x**2 for x in numbers),
133
[numbers]
134
)
135
136
return html.div(f"Sum of squares: {expensive_result}")
137
```
138
139
### Ref Hook
140
141
The `use_ref` hook creates a mutable reference that persists across renders.
142
143
```python { .api }
144
def use_ref(initial_value: T = None) -> Ref[T]: ...
145
146
class Ref[T]:
147
current: T
148
def __init__(self, initial_value: T = None): ...
149
```
150
151
**Returns:** Ref object with `current` attribute
152
153
**Usage Examples:**
154
155
```python
156
@component
157
def FocusInput():
158
input_ref = use_ref(None)
159
160
def focus_input():
161
if input_ref.current:
162
input_ref.current.focus()
163
164
return html.div(
165
html.input({"ref": input_ref}),
166
html.button({"onClick": focus_input}, "Focus Input")
167
)
168
169
@component
170
def PreviousValue(value: int):
171
previous_ref = use_ref(None)
172
173
use_effect(lambda: setattr(previous_ref, 'current', value))
174
175
return html.div(f"Current: {value}, Previous: {previous_ref.current}")
176
```
177
178
### Reducer Hook
179
180
The `use_reducer` hook manages complex state with reducer pattern.
181
182
```python { .api }
183
def use_reducer(
184
reducer: Callable[[T, Any], T],
185
initial_value: T,
186
init: Callable[[T], T] | None = None
187
) -> tuple[T, Callable[[Any], None]]: ...
188
```
189
190
**Parameters:**
191
- `reducer`: Function that takes (state, action) and returns new state
192
- `initial_value`: Initial state value
193
- `init`: Optional function to compute initial state lazily
194
195
**Returns:** Tuple of `(current_state, dispatch_function)`
196
197
**Usage Examples:**
198
199
```python
200
def counter_reducer(state, action):
201
if action["type"] == "increment":
202
return state + 1
203
elif action["type"] == "decrement":
204
return state - 1
205
elif action["type"] == "reset":
206
return 0
207
return state
208
209
@component
210
def CounterWithReducer():
211
count, dispatch = use_reducer(counter_reducer, 0)
212
213
return html.div(
214
html.h1(f"Count: {count}"),
215
html.button({"onClick": lambda: dispatch({"type": "increment"})}, "Increment"),
216
html.button({"onClick": lambda: dispatch({"type": "decrement"})}, "Decrement"),
217
html.button({"onClick": lambda: dispatch({"type": "reset"})}, "Reset")
218
)
219
```
220
221
### Context Hooks
222
223
Context hooks enable data sharing across component trees without prop drilling.
224
225
```python { .api }
226
def create_context(default_value: T = None) -> Context[T]: ...
227
def use_context(context: Context[T]) -> T: ...
228
```
229
230
**Usage Examples:**
231
232
```python
233
# Create context
234
ThemeContext = create_context("light")
235
236
@component
237
def ThemeProvider(*children, theme: str = "light"):
238
return ThemeContext.Provider({"value": theme}, *children)
239
240
@component
241
def ThemedButton():
242
theme = use_context(ThemeContext)
243
244
return html.button(
245
{"className": f"btn btn-{theme}"},
246
"Themed Button"
247
)
248
```
249
250
### Specialized Hooks
251
252
Additional hooks for specific use cases:
253
254
```python { .api }
255
def use_connection() -> Connection: ...
256
def use_location() -> Location: ...
257
def use_debug_value(value: Any, formatter: Callable[[Any], str] | None = None) -> None: ...
258
def use_scope() -> dict[str, Any]: ...
259
def use_exception() -> Callable[[Exception], None]: ...
260
```
261
262
**Usage Examples:**
263
264
```python
265
@component
266
def ConnectionInfo():
267
connection = use_connection()
268
return html.div(f"Connected from: {connection.get('remote_addr')}")
269
270
@component
271
def LocationAware():
272
location = use_location()
273
return html.div(f"Current path: {location.get('pathname')}")
274
275
@component
276
def DebuggableComponent():
277
count, set_count = use_state(0)
278
use_debug_value(count, lambda c: f"Count: {c}")
279
280
return html.div(f"Count: {count}")
281
```
282
283
### Hook Rules
284
285
1. **Only call hooks from component functions** - Never call hooks from regular functions, loops, or conditionals
286
2. **Call hooks in the same order** - Don't call hooks inside loops, conditions, or nested functions
287
3. **Hook dependencies** - Always include all values used inside effects in the dependency array
288
289
```python
290
# ❌ Wrong - conditional hook
291
@component
292
def BadComponent(condition: bool):
293
if condition:
294
count, set_count = use_state(0) # Don't do this
295
return html.div()
296
297
# ✅ Correct - hooks at top level
298
@component
299
def GoodComponent(condition: bool):
300
count, set_count = use_state(0)
301
302
if condition:
303
return html.div(f"Count: {count}")
304
return html.div("No count")
305
```