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 call state.doubled, this is the state proxy. If you call snap.doubled, this is the snap 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 from valtio/utils or derive from the derive-valtio package.