Computed Properties
Valtio supports computed properties using native JavaScript getters and setters on your proxy object. This allows you to create derived state that automatically updates when its dependencies change.
Note: Getters are an advanced JavaScript feature. While they work as expected, be mindful of the behavior of
this
inside them.
Using a Simple Getter
You can define a getter on your proxy object to compute a value based on other properties.
const state = proxy({
count: 1,
get doubled() {
return this.count * 2;
},
});
console.log(state.doubled); // 2
state.count = 10;
console.log(state.doubled); // 20
When used with useSnapshot
in React, components that access snap.doubled
will automatically re-render when state.count
changes.
Using Getters and Setters
You can also define a setter to allow a computed property to be "settable," which in turn updates the source state.
const state = proxy({
count: 1,
get doubled() {
return state.count * 2;
},
set doubled(newValue) {
state.count = newValue / 2;
},
});
state.doubled = 4;
console.log(state.count); // 2
Caching with proxy-memoize
By default, getters on the state
proxy are re-calculated on every access. If your computed property is expensive, you can memoize it using Valtio's sister library, proxy-memoize
.
This library uses the same usage-tracking mechanism as useSnapshot
, so the getter will only be re-calculated if the specific properties it depends on have changed.
import { proxy, snapshot } from 'valtio';
import { memoize } from 'proxy-memoize';
const memoizedDoubled = memoize((snap) => snap.count * 2);
const state = proxy({
count: 1,
text: 'hello',
get doubled() {
// Pass the snapshot to the memoized function
return memoizedDoubled(snapshot(state));
},
});
In this setup, changing state.text
will not cause memoizedDoubled
to re-run.
Rules and Gotchas
this
Context: Inside a getter,this
refers to the object the getter was called on. If you callstate.doubled
,this
is thestate
proxy. If you callsnap.doubled
,this
is thesnap
object.-
Sibling Properties Only: A computed property should generally only reference sibling properties (properties on the same object level). Accessing parent or other external state can lead to unexpected behavior.
// ✅ GOOD: `this` refers to the current object const user = proxy({ name: 'John', get greeting() { return 'Hello ' + this.name; }, }); // ❌ BAD: `this` points to `greetings`, not `state` const state = proxy({ user: { name: 'John' }, greetings: { get greetingEn() { return 'Hello ' + this.user.name; // `this.user` is undefined }, }, });
-
Alternatives for Cross-State Derivation: If you need to derive state from multiple different proxies, consider using
watch
fromvaltio/utils
orderive
from thederive-valtio
package.