CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-valtio

Proxy-state management library that makes state simple for React and vanilla JavaScript applications

Pending
Overview
Eval results
Files

react-integration.mddocs/

React Integration

React hooks optimized for Valtio's proxy system, providing automatic re-rendering only when accessed properties change. The integration offers maximum performance with minimal boilerplate.

Capabilities

useSnapshot Hook

Creates a local snapshot that catches changes and triggers re-renders only when accessed parts of the state change. This is the primary hook for reading Valtio state in React components.

/**
 * Create a local snapshot that catches changes with render optimization
 * Rule of thumb: read from snapshots, mutate the source
 * The component will only re-render when accessed parts change
 * @param proxyObject - The proxy object to create a snapshot from
 * @param options - Configuration options
 * @param options.sync - If true, notifications happen synchronously
 * @returns A wrapped snapshot in a proxy for render optimization
 */
function useSnapshot<T extends object>(
  proxyObject: T,
  options?: { sync?: boolean }
): Snapshot<T>;

Usage Examples:

import { proxy, useSnapshot } from "valtio";

const state = proxy({ 
  count: 0, 
  text: "hello",
  user: { name: "Alice", age: 25 }
});

// Basic usage - only re-renders when count changes
function Counter() {
  const snap = useSnapshot(state);
  return (
    <div>
      {snap.count}
      <button onClick={() => ++state.count}>+1</button>
    </div>
  );
}

// Nested property access - only re-renders when user.name changes
function UserName() {
  const snap = useSnapshot(state);
  return (
    <div>
      Hello, {snap.user.name}!
      <button onClick={() => state.user.name = "Bob"}>
        Change Name
      </button>
    </div>
  );
}

// Partial destructuring - only re-renders when user object changes
function UserProfile() {
  const { user } = useSnapshot(state);
  return (
    <div>
      {user.name} ({user.age})
    </div>
  );
}

// Synchronous updates for immediate re-renders
function ImmediateCounter() {
  const snap = useSnapshot(state, { sync: true });
  return <div>{snap.count}</div>;
}

Important Notes:

  • Read from snapshots in render, mutate the original proxy
  • The component only re-renders when properties you actually access change
  • Snapshot objects are deeply readonly for type safety
  • Each useSnapshot call creates a new proxy for render optimization

useProxy Hook

Takes a proxy and returns a new proxy that can be used safely in both React render functions and event callbacks. The root reference is replaced on every render, but nested keys remain stable.

/**
 * Takes a proxy and returns a new proxy for use in both render and callbacks
 * The root reference is replaced on every render, but keys below it are stable
 * until they're intentionally mutated
 * @param proxy - The proxy object to wrap
 * @param options - Options passed to useSnapshot internally
 * @returns A new proxy for render and callback usage
 */
function useProxy<T extends object>(
  proxy: T,
  options?: { sync?: boolean }
): T;

Usage Examples:

import { proxy, useProxy } from "valtio";

const globalState = proxy({ count: 0, items: [] });

// Export custom hook from your store for better ergonomics
export const useStore = () => useProxy(globalState);

// Component usage
function Counter() {
  const store = useStore();
  
  // Can read in render (like snapshot)
  const count = store.count;
  
  // Can mutate in callbacks (like original proxy)
  const increment = () => { store.count++; };
  
  return (
    <div>
      {count}
      <button onClick={increment}>+1</button>
    </div>
  );
}

// Direct usage without custom hook
function TodoList() {
  const state = useProxy(globalState);
  
  return (
    <div>
      <button onClick={() => state.items.push(`Item ${state.items.length}`)}>
        Add Item
      </button>
      {state.items.map((item, index) => (
        <div key={index}>{item}</div>
      ))}
    </div>
  );
}

Benefits over useSnapshot:

  • Single reference for both reading and writing
  • No need to import both proxy and snapshot references
  • More ergonomic for complex interactions
  • Stable references for nested objects until mutations occur

Important Notes:

  • Uses useSnapshot internally for render optimization
  • Root reference changes on every render (intentional for React optimization)
  • May not work properly with React Compiler due to intentional render behavior
  • Best suited for components that both read and write to the same state

React Integration Patterns

Component State

import { proxy, useSnapshot } from "valtio";

// Local component state
function TodoApp() {
  const [state] = useState(() => proxy({ 
    todos: [],
    filter: 'all'
  }));
  
  const snap = useSnapshot(state);
  
  const addTodo = (text: string) => {
    state.todos.push({ id: Date.now(), text, done: false });
  };
  
  return (
    <div>
      {snap.todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} />
      ))}
    </div>
  );
}

Global State

import { proxy, useSnapshot } from "valtio";

// Global state
export const appState = proxy({
  user: null,
  theme: 'light',
  notifications: []
});

// Multiple components can use the same state
function Header() {
  const { user, theme } = useSnapshot(appState);
  return (
    <header className={theme}>
      Welcome, {user?.name}
    </header>
  );
}

function Settings() {
  const { theme } = useSnapshot(appState);
  return (
    <button onClick={() => appState.theme = theme === 'light' ? 'dark' : 'light'}>
      Toggle Theme
    </button>
  );
}

Conditional Re-rendering

function OptimizedComponent() {
  const snap = useSnapshot(state);
  
  // Only accesses count, so only re-renders when count changes
  if (snap.count > 10) {
    return <div>Count is high: {snap.count}</div>;
  }
  
  // If condition is false, text is never accessed
  // Component won't re-render when text changes
  return <div>Count is low, text: {snap.text}</div>;
}

Types

type Snapshot<T> = T extends { $$valtioSnapshot: infer S }
  ? S
  : T extends SnapshotIgnore
    ? T
    : T extends object
      ? { readonly [K in keyof T]: Snapshot<T[K]> }
      : T;

interface Options {
  sync?: boolean;
}

Install with Tessl CLI

npx tessl i tessl/npm-valtio

docs

core-proxy.md

index.md

react-integration.md

utilities.md

tile.json