Core Concepts
Valtio's design is centered around a few simple but powerful concepts. Understanding them will help you use the library effectively.
1. The Proxy State
The foundation of Valtio is the proxy
function. You create a state by wrapping a plain JavaScript object with it.
import { proxy } from 'valtio';
const state = proxy({
count: 0,
user: { name: 'John' },
items: ['A', 'B']
});
This state
object looks and feels just like a regular JavaScript object, but it's now a special proxy that tracks any mutations made to it or its nested properties.
2. Direct Mutation
With Valtio, you don't need setters, reducers, or special update functions. You change the state by directly mutating the proxy object.
// From anywhere in your application
state.count++;
state.user.name = 'Jane';
state.items.push('C');
This is possible because the proxy intercepts these operations and notifies Valtio that a change has occurred.
3. The Snapshot for React
While you can mutate the proxy state from anywhere, you should never use the mutable proxy directly for rendering in React. Doing so can lead to unexpected behavior and break React's rendering model.
Instead, you use the useSnapshot
hook to get an immutable, read-only version of the state.
import { useSnapshot } from 'valtio';
function UserDisplay() {
const snap = useSnapshot(state);
// `snap` is an immutable snapshot. It's safe to use for rendering.
return <div>User: {snap.user.name}</div>;
}
This leads to the core rule of thumb in Valtio:
Read from snapshots, mutate the source.
- In your component's render logic, always read from the
snap
object returned byuseSnapshot
. - In callbacks and event handlers, always mutate the original
state
proxy.
function Counter() {
const snap = useSnapshot(state); // READ from snapshot
const increment = () => {
++state.count; // MUTATE the source proxy
};
return (
<div>
Count: {snap.count}
<button onClick={increment}>+1</button>
</div>
);
}
4. Automatic Render Optimization
Valtio is finely tuned for performance. When you use useSnapshot
, Valtio tracks exactly which properties of the state your component accesses during rendering.
const state = proxy({ a: 1, b: 2 });
function ComponentA() {
const snap = useSnapshot(state);
return <div>A: {snap.a}</div>;
}
function ComponentB() {
const snap = useSnapshot(state);
return <div>B: {snap.b}</div>;
}
In this example:
- If
state.a
changes, onlyComponentA
will re-render. - If
state.b
changes, onlyComponentB
will re-render.
This tracking is automatic and works for nested properties as well. It ensures that your components only update when absolutely necessary, without any need for manual memoization like React.memo
or useMemo
.