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
derivefor 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