0
# React Integration
1
2
React hooks optimized for Valtio's proxy system, providing automatic re-rendering only when accessed properties change. The integration offers maximum performance with minimal boilerplate.
3
4
## Capabilities
5
6
### useSnapshot Hook
7
8
Creates a local snapshot that catches changes and triggers re-renders only when accessed parts of the state change. This is the primary hook for reading Valtio state in React components.
9
10
```typescript { .api }
11
/**
12
* Create a local snapshot that catches changes with render optimization
13
* Rule of thumb: read from snapshots, mutate the source
14
* The component will only re-render when accessed parts change
15
* @param proxyObject - The proxy object to create a snapshot from
16
* @param options - Configuration options
17
* @param options.sync - If true, notifications happen synchronously
18
* @returns A wrapped snapshot in a proxy for render optimization
19
*/
20
function useSnapshot<T extends object>(
21
proxyObject: T,
22
options?: { sync?: boolean }
23
): Snapshot<T>;
24
```
25
26
**Usage Examples:**
27
28
```typescript
29
import { proxy, useSnapshot } from "valtio";
30
31
const state = proxy({
32
count: 0,
33
text: "hello",
34
user: { name: "Alice", age: 25 }
35
});
36
37
// Basic usage - only re-renders when count changes
38
function Counter() {
39
const snap = useSnapshot(state);
40
return (
41
<div>
42
{snap.count}
43
<button onClick={() => ++state.count}>+1</button>
44
</div>
45
);
46
}
47
48
// Nested property access - only re-renders when user.name changes
49
function UserName() {
50
const snap = useSnapshot(state);
51
return (
52
<div>
53
Hello, {snap.user.name}!
54
<button onClick={() => state.user.name = "Bob"}>
55
Change Name
56
</button>
57
</div>
58
);
59
}
60
61
// Partial destructuring - only re-renders when user object changes
62
function UserProfile() {
63
const { user } = useSnapshot(state);
64
return (
65
<div>
66
{user.name} ({user.age})
67
</div>
68
);
69
}
70
71
// Synchronous updates for immediate re-renders
72
function ImmediateCounter() {
73
const snap = useSnapshot(state, { sync: true });
74
return <div>{snap.count}</div>;
75
}
76
```
77
78
**Important Notes:**
79
80
- Read from snapshots in render, mutate the original proxy
81
- The component only re-renders when properties you actually access change
82
- Snapshot objects are deeply readonly for type safety
83
- Each `useSnapshot` call creates a new proxy for render optimization
84
85
### useProxy Hook
86
87
Takes a proxy and returns a new proxy that can be used safely in both React render functions and event callbacks. The root reference is replaced on every render, but nested keys remain stable.
88
89
```typescript { .api }
90
/**
91
* Takes a proxy and returns a new proxy for use in both render and callbacks
92
* The root reference is replaced on every render, but keys below it are stable
93
* until they're intentionally mutated
94
* @param proxy - The proxy object to wrap
95
* @param options - Options passed to useSnapshot internally
96
* @returns A new proxy for render and callback usage
97
*/
98
function useProxy<T extends object>(
99
proxy: T,
100
options?: { sync?: boolean }
101
): T;
102
```
103
104
**Usage Examples:**
105
106
```typescript
107
import { proxy, useProxy } from "valtio";
108
109
const globalState = proxy({ count: 0, items: [] });
110
111
// Export custom hook from your store for better ergonomics
112
export const useStore = () => useProxy(globalState);
113
114
// Component usage
115
function Counter() {
116
const store = useStore();
117
118
// Can read in render (like snapshot)
119
const count = store.count;
120
121
// Can mutate in callbacks (like original proxy)
122
const increment = () => { store.count++; };
123
124
return (
125
<div>
126
{count}
127
<button onClick={increment}>+1</button>
128
</div>
129
);
130
}
131
132
// Direct usage without custom hook
133
function TodoList() {
134
const state = useProxy(globalState);
135
136
return (
137
<div>
138
<button onClick={() => state.items.push(`Item ${state.items.length}`)}>
139
Add Item
140
</button>
141
{state.items.map((item, index) => (
142
<div key={index}>{item}</div>
143
))}
144
</div>
145
);
146
}
147
```
148
149
**Benefits over useSnapshot:**
150
151
- Single reference for both reading and writing
152
- No need to import both proxy and snapshot references
153
- More ergonomic for complex interactions
154
- Stable references for nested objects until mutations occur
155
156
**Important Notes:**
157
158
- Uses `useSnapshot` internally for render optimization
159
- Root reference changes on every render (intentional for React optimization)
160
- May not work properly with React Compiler due to intentional render behavior
161
- Best suited for components that both read and write to the same state
162
163
## React Integration Patterns
164
165
### Component State
166
167
```typescript
168
import { proxy, useSnapshot } from "valtio";
169
170
// Local component state
171
function TodoApp() {
172
const [state] = useState(() => proxy({
173
todos: [],
174
filter: 'all'
175
}));
176
177
const snap = useSnapshot(state);
178
179
const addTodo = (text: string) => {
180
state.todos.push({ id: Date.now(), text, done: false });
181
};
182
183
return (
184
<div>
185
{snap.todos.map(todo => (
186
<TodoItem key={todo.id} todo={todo} />
187
))}
188
</div>
189
);
190
}
191
```
192
193
### Global State
194
195
```typescript
196
import { proxy, useSnapshot } from "valtio";
197
198
// Global state
199
export const appState = proxy({
200
user: null,
201
theme: 'light',
202
notifications: []
203
});
204
205
// Multiple components can use the same state
206
function Header() {
207
const { user, theme } = useSnapshot(appState);
208
return (
209
<header className={theme}>
210
Welcome, {user?.name}
211
</header>
212
);
213
}
214
215
function Settings() {
216
const { theme } = useSnapshot(appState);
217
return (
218
<button onClick={() => appState.theme = theme === 'light' ? 'dark' : 'light'}>
219
Toggle Theme
220
</button>
221
);
222
}
223
```
224
225
### Conditional Re-rendering
226
227
```typescript
228
function OptimizedComponent() {
229
const snap = useSnapshot(state);
230
231
// Only accesses count, so only re-renders when count changes
232
if (snap.count > 10) {
233
return <div>Count is high: {snap.count}</div>;
234
}
235
236
// If condition is false, text is never accessed
237
// Component won't re-render when text changes
238
return <div>Count is low, text: {snap.text}</div>;
239
}
240
```
241
242
## Types
243
244
```typescript { .api }
245
type Snapshot<T> = T extends { $$valtioSnapshot: infer S }
246
? S
247
: T extends SnapshotIgnore
248
? T
249
: T extends object
250
? { readonly [K in keyof T]: Snapshot<T[K]> }
251
: T;
252
253
interface Options {
254
sync?: boolean;
255
}
256
```