proxy
The proxy
function is the core of Valtio. It takes a plain JavaScript object and returns a reactive proxy that tracks changes to itself and all nested objects.
import { proxy } from 'valtio';
const state = proxy({ count: 0, text: 'hello' });
Direct Mutation
You can mutate the proxy object directly, just like a regular JavaScript object. These mutations will be tracked and will trigger updates in subscribed components.
setInterval(() => {
++state.count;
}, 1000);
Optimizations
Valtio includes two key optimizations for performance:
-
No-op Detection: Updates that set a property to its current value are ignored. Subscribers are not notified if the value doesn't actually change.
const state = proxy({ count: 0 }); state.count = 0; // This has no effect and does not trigger an update.
-
Batching: Multiple mutations within the same synchronous event loop tick are batched together. Subscribers are notified only once after all mutations are complete.
const state = proxy({ count: 0, text: 'hello' }); // Subscribers will be notified only once after both mutations. state.count = 1; state.text = 'world';
Nested Proxies
Proxies can be nested. Any object you assign to a property of a proxy will also become a proxy, and its changes will be tracked.
import { proxy } from 'valtio';
const personState = proxy({ name: 'Timo', role: 'admin' });
const authState = proxy({ status: 'loggedIn', user: personState });
// This mutation will be tracked by both authState and personState.
authState.user.name = 'Nina';
Promises in Proxies
Valtio state can hold promises. When used with useSnapshot
in React, this enables seamless integration with React Suspense. See the Async guide for more details.
import { proxy } from 'valtio';
const dataState = proxy({
data: new Promise((resolve) => setTimeout(() => resolve('Boom!'), 1000)),
});
Gotchas
-
Reassigning the Root Proxy: Do not reassign the entire state object. This will replace the proxy with a plain object, and state tracking will stop.
let state = proxy({ user: { name: 'Timo' } }); // ❌ This breaks reactivity. state = { user: { name: 'Nina' } }; // ✅ This works as expected. state.user.name = 'Nina';
-
Unproxyable Objects: Not all objects can be proxied effectively. Generally, only serializable plain objects, arrays, and classes work best. Avoid special objects like DOM elements, Maps, or Sets. For these cases, use
ref
.// ❌ These won't be reactive. const state = proxy({ map: new Map(), // Use proxyMap utility instead storage: localStorage }); // ✅ Classes work. class User { constructor(first, last) { this.first = first; this.last = last; } get fullName() { return `${this.first} ${this.last}`; } } const userState = proxy(new User('Timo', 'Kivinen'));