When building React apps, state management is unavoidable. Most of us start with useState, and for good reason — it’s simple, predictable, and built into React.
But as applications grow, you may hear about something called “proxy state.” Libraries like MobX and Valtio often come up in this context.
So what exactly is proxy state?
How does it differ from useState?
And when should you care?
Let’s break it down.
Understanding useState first
useState works on a snapshot model.
const [count, setCount] = useState(0);
setCount(count + 1);
Each update:
- does not mutate existing state
- replaces it with a new value
- triggers a component re-render
With objects, this usually means copying:
const [user, setUser] = useState({ name: "Alex", age: 20 });
setUser(prev => ({
...prev,
age: 21
}));
React only knows something changed because the reference changed. It does not track which property changed.
This approach is:
- predictable
- aligned with functional programming
- extremely well optimized
But it can get verbose and inefficient in large applications.
What “proxy state” means
React itself does not provide proxy state.
The term usually refers to state management libraries that use JavaScript Proxies under the hood.
Examples:
- MobX
- Valtio
- reactive store systems inspired by Solid or Vue
A JavaScript Proxy can intercept operations like:
- reading a property
- writing a property
This allows libraries to automatically track:
- what parts of state a component used
- what exactly changed
So instead of replacing state, you mutate it directly.
state.count++;
state.user.name = "Fayaz";
The proxy detects the mutation and notifies only the components that depend on those properties.
The mental model shift
The biggest difference is how you think about state.
useState → snapshot-based
- state is immutable
- updates replace values
- re-render happens at the component level
Proxy state → reactive objects
- state is mutable
- updates are property-level
- re-render happens at the usage level
You stop thinking about “setting state” and start thinking about “changing state.”
Example: same logic, two styles
With useState
const [cart, setCart] = useState({ items: [], total: 0 });
function addItem(item) {
setCart(prev => ({
...prev,
items: [...prev.items, item],
total: prev.total + item.price
}));
}
You must:
- copy objects
- replace state
- manually keep values in sync
With proxy state (conceptual)
cart.items.push(item);
cart.total += item.price;
The proxy system:
- detects the mutation
- tracks dependencies
- re-renders only what actually used
cart.itemsorcart.total
Your code feels closer to plain JavaScript.
Dependency tracking: the real power
React with useState doesn’t know which properties you accessed.
const name = user.profile.name;
If any part of user changes, the entire component re-renders.
Proxy-based systems track access at runtime:
- Component A reads
user.name - Component B reads
user.age - Only the component that depends on the changed property updates
This is called fine-grained reactivity, and it’s the main technical advantage of proxy state.
Performance implications
-
useState→ coarse-grained updates - Proxy state → fine-grained updates
In large apps with deeply nested global state, proxy systems can significantly reduce unnecessary re-renders.
That said, useState is:
- simpler to debug
- fully aligned with concurrent React
- more than fast enough for most UI state
When proxy state shines
Proxy state is especially useful when:
- your app has a large global store
- many components read tiny slices of state
- state is deeply nested
- performance tuning becomes difficult
- you prefer mutation-style APIs
When useState is still the best choice
Stick with useState when:
- state is local to a component
- you’re managing UI concerns (forms, modals, toggles)
- you want to rely only on core React APIs
- explicit, predictable updates matter most
For most UI logic, useState is still the cleanest solution.
Proxy state is not Immer
A common confusion: Immer also uses proxies, but it does not create reactive state.
setState(draft => {
draft.count++;
});
Immer lets you write mutable code while still producing an immutable snapshot.
Proxy state libraries keep the proxy object alive and make it reactive.
Final takeaway
Proxy state differs from useState because it uses JavaScript Proxies to:
- track property access
- detect mutations
- trigger fine-grained updates
useState works by replacing values and re-rendering components.
Proxy state works by mutating objects and letting a reactive system decide what should update.
Neither is better.
They solve different problems at different scales.
Top comments (0)