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.