useSnapshot
The useSnapshot
hook is the bridge between your Valtio state and your React components. It takes a proxy object and returns a local, immutable snapshot of its state.
This hook is designed for render optimization. It wraps the snapshot in another proxy that tracks which properties are accessed during the render cycle. This ensures that your component only re-renders when the specific data it uses has changed.
Usage
The Core Pattern: Read from Snapshot, Mutate Proxy
In your component's render logic, always read data from the snap
object. In callbacks or event handlers, always mutate the original proxy
object.
import { proxy, useSnapshot } from 'valtio';
const state = proxy({ count: 0 });
function Counter() {
const snap = useSnapshot(state);
return (
<div>
{/* Read from the immutable snapshot for rendering */}
{snap.count}
<button
onClick={() => {
// Mutate the original proxy in callbacks
++state.count;
}}
>
+1
</button>
</div>
);
}
Working with Nested Objects
Every object inside your proxy also becomes a proxy. You can pass these nested proxies to useSnapshot
to scope a component's updates to just that part of the state.
const state = proxy({ profile: { name: 'Valtio' }, settings: { theme: 'dark' } });
function ProfileName() {
// This component only re-renders when state.profile changes.
const snap = useSnapshot(state.profile);
return <div>{snap.name}</div>;
}
Passing Snapshots to Child Components
A parent component can pass parts of its snapshot down to child components as props. React's shallow prop comparison works perfectly here because Valtio guarantees object stability. If a part of the state tree hasn't changed, its snapshot will be the exact same object reference (===
).
const state = proxy({ books: [{ id: 1, title: 'Book 1' }, { id: 2, title: 'Book 2' }] });
function BookList() {
const snap = useSnapshot(state);
return (
<div>
{snap.books.map((book) => (
<BookView key={book.id} book={book} />
))}
</div>
);
}
// BookView receives a snapshot as a prop
const BookView = ({ book }) => <div>{book.title}</div>;
If state.books[1].title
is changed, only the second BookView
will re-render (along with BookList
). The first BookView
will not, because its book
prop is referentially stable.
Gotchas
Be careful not to replace a nested proxy object entirely, as this can break subscriptions in components that are subscribed to the old object reference.
// Initial state
const state = proxy({ profile: { name: 'Valtio' } });
// A component subscribes to the original profile object
const childState = state.profile;
// If you replace the object, childState is now stale
state.profile = { name: 'New Name' };
// childState still points to the old, detached proxy
console.log(childState.name); // 'Valtio'
To avoid this, always subscribe to the parent object in your component and access the nested property from its snapshot.
// ✅ Correct way
function ProfileName() {
const snap = useSnapshot(state);
return <div>{snap.profile.name}</div>;
}
Dev Mode Debug Values
In development mode, useSnapshot
uses React's useDebugValue
hook to display the property paths accessed during the last render. This is useful for debugging why a component is or isn't re-rendering.
Note: The debug value reflects the previous render's accessed paths.