Skip to main content

Valtio Adapter

Track Valtio proxy state changes with React Dev Debugger for better debugging.

Installation​

npm install react-dev-debugger valtio

Basic Setup​

store.ts
import { proxy } from 'valtio';
import { createValtioAdapter } from 'react-dev-debugger/adapters/valtio';

// Create your proxy state
export const state = proxy({
count: 0,
user: null,
todos: [],
});

// Connect debugger
createValtioAdapter(state, {
name: 'App State',
});

Live Playground​

Counter Store

Count: 0

User Store

Age: 25

Todos Store

    Total: 0Completed: 0

    Proxy States

    Valtio Adapter Configuration

    import { proxy } from 'valtio';
    import { createValtioAdapter } from 'react-dev-debugger/adapters/valtio';
    
    // Create proxy stores
    const counterStore = proxy({
      count: 0,
      increment: () => counterStore.count++,
      decrement: () => counterStore.count--,
    });
    
    const userStore = proxy({
      name: 'John',
      email: 'john@example.com',
      updateName: (name) => userStore.name = name,
    });
    
    // Create adapter
    const adapter = createValtioAdapter({
      name: 'My Store',
      proxies: {
        counter: counterStore,
        user: userStore,
      },
    });
    
    // Subscribe to changes
    adapter.subscribe((state) => {
      console.log('Proxy states:', state.proxies);
    });

    Usage in Components​

    Counter.tsx
    import { useSnapshot } from 'valtio';
    import { state } from './store';

    export function Counter() {
    const snap = useSnapshot(state);

    return (
    <div>
    <h2>Count: {snap.count}</h2>
    <button onClick={() => state.count++}>Increment</button>
    <button onClick={() => state.count--}>Decrement</button>
    </div>
    );
    }

    Nested State​

    Valtio handles nested objects automatically:

    store.ts
    import { proxy } from 'valtio';
    import { createValtioAdapter } from 'react-dev-debugger/adapters/valtio';

    export const state = proxy({
    user: {
    name: '',
    email: '',
    preferences: {
    theme: 'dark',
    notifications: true,
    },
    },
    cart: {
    items: [],
    total: 0,
    },
    });

    createValtioAdapter(state, {
    name: 'App Store',
    });
    UserProfile.tsx
    import { useSnapshot } from 'valtio';
    import { state } from './store';

    export function UserProfile() {
    const snap = useSnapshot(state);

    const updateName = (name: string) => {
    state.user.name = name;
    };

    const toggleTheme = () => {
    state.user.preferences.theme =
    snap.user.preferences.theme === 'dark' ? 'light' : 'dark';
    };

    return (
    <div>
    <input
    value={snap.user.name}
    onChange={(e) => updateName(e.target.value)}
    />
    <button onClick={toggleTheme}>
    Theme: {snap.user.preferences.theme}
    </button>
    </div>
    );
    }

    Actions Pattern​

    Organize state mutations with actions:

    store.ts
    import { proxy } from 'valtio';
    import { createValtioAdapter } from 'react-dev-debugger/adapters/valtio';

    interface Todo {
    id: number;
    text: string;
    completed: boolean;
    }

    export const state = proxy<{
    todos: Todo[];
    }>({
    todos: [],
    });

    // Actions
    export const actions = {
    addTodo(text: string) {
    state.todos.push({
    id: Date.now(),
    text,
    completed: false,
    });
    },

    toggleTodo(id: number) {
    const todo = state.todos.find(t => t.id === id);
    if (todo) {
    todo.completed = !todo.completed;
    }
    },

    deleteTodo(id: number) {
    const index = state.todos.findIndex(t => t.id === id);
    if (index !== -1) {
    state.todos.splice(index, 1);
    }
    },

    clearCompleted() {
    state.todos = state.todos.filter(t => !t.completed);
    },
    };

    createValtioAdapter(state, {
    name: 'Todo Store',
    });
    TodoList.tsx
    import { useState } from 'react';
    import { useSnapshot } from 'valtio';
    import { state, actions } from './store';

    export function TodoList() {
    const snap = useSnapshot(state);
    const [input, setInput] = useState('');

    const handleAdd = () => {
    if (input.trim()) {
    actions.addTodo(input);
    setInput('');
    }
    };

    return (
    <div>
    <h1>Valtio Todo List</h1>

    <div>
    <input
    value={input}
    onChange={(e) => setInput(e.target.value)}
    onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
    />
    <button onClick={handleAdd}>Add</button>
    </div>

    <ul>
    {snap.todos.map((todo) => (
    <li key={todo.id}>
    <input
    type="checkbox"
    checked={todo.completed}
    onChange={() => actions.toggleTodo(todo.id)}
    />
    <span style={{
    textDecoration: todo.completed ? 'line-through' : 'none'
    }}>
    {todo.text}
    </span>
    <button onClick={() => actions.deleteTodo(todo.id)}>
    Delete
    </button>
    </li>
    ))}
    </ul>

    {snap.todos.some(t => t.completed) && (
    <button onClick={actions.clearCompleted}>
    Clear Completed
    </button>
    )}
    </div>
    );
    }

    Computed Values​

    Use derive for computed values:

    store.ts
    import { proxy } from 'valtio';
    import { derive } from 'valtio/utils';
    import { createValtioAdapter } from 'react-dev-debugger/adapters/valtio';

    export const state = proxy({
    items: [],
    filter: 'all',
    });

    export const derived = derive({
    filteredItems: (get) => {
    const items = get(state).items;
    const filter = get(state).filter;

    if (filter === 'active') {
    return items.filter(item => !item.completed);
    }
    if (filter === 'completed') {
    return items.filter(item => item.completed);
    }
    return items;
    },
    stats: (get) => {
    const items = get(state).items;
    return {
    total: items.length,
    completed: items.filter(i => i.completed).length,
    active: items.filter(i => !i.completed).length,
    };
    },
    });

    createValtioAdapter(state, { name: 'State' });
    createValtioAdapter(derived, { name: 'Derived' });

    Subscriptions​

    Track specific state changes:

    import { subscribe } from 'valtio';
    import { state } from './store';

    // Subscribe to all changes
    const unsubscribe = subscribe(state, () => {
    console.log('State changed:', state);
    });

    // Cleanup
    unsubscribe();

    Configuration Options​

    interface ValtioAdapterOptions {
    // Display name
    name?: string;

    // Maximum snapshots to keep
    maxSnapshots?: number;

    // Transform state before displaying
    stateTransform?: (state: any) => any;
    }

    Debugging Tips​

    Mutation Tracking​

    Every direct mutation to the proxy is tracked and appears in the timeline.

    Nested Updates​

    The debugger shows the exact path that changed (e.g., user.preferences.theme).

    Performance​

    Valtio's proxy-based approach means minimal overhead for tracking.

    Best Practices​

    ✅ Do's​

    • Use actions to organize mutations
    • Keep state flat when possible
    • Use derive for computed values
    • Leverage snapshots for reading

    ❌ Don'ts​

    • Don't mutate snapshots
    • Avoid deep nesting (impacts performance)
    • Don't mix proxy and non-proxy objects

    Next Steps​