valtio
Manage React state with Valtio — proxy-based state that just works. Use when someone asks to "simple React state management", "Valtio", "proxy state", "mutable state in React", "alternative to Zustand/Redux", or "state management without boilerplate". Covers proxy state, subscriptions, computed values, and devtools.
Usage
Getting Started
- Install the skill using the command above
- Open your AI coding agent (Claude Code, Codex, Gemini CLI, or Cursor)
- Reference the skill in your prompt
- The AI will use the skill's capabilities automatically
Example Prompts
- "Review the open pull requests and summarize what needs attention"
- "Generate a changelog from the last 20 commits on the main branch"
Documentation
Overview
Valtio makes React state management feel like plain JavaScript — mutate objects directly and React re-renders automatically. No reducers, no actions, no selectors. Wrap an object in proxy(), mutate it anywhere, and components that read the changed properties re-render. Based on JavaScript Proxy, it tracks which properties each component uses and only re-renders when those specific properties change.
When to Use
- Want the simplest possible state management
- Tired of Redux boilerplate or Zustand's
set()function - Sharing state between components without prop drilling
- State that's accessed/modified outside React (event handlers, WebSocket callbacks)
- Team prefers mutable patterns over immutable
Instructions
Setup
npm install valtio
Basic Store
// store/app.ts — Define state as a plain object
import { proxy, useSnapshot } from "valtio";
export const appState = proxy({
user: null as { name: string; email: string } | null,
theme: "light" as "light" | "dark",
notifications: [] as Array<{ id: string; text: string; read: boolean }>,
sidebar: { open: true, width: 280 },
});
// Mutate directly — React components auto-update
export function login(user: { name: string; email: string }) {
appState.user = user;
}
export function toggleTheme() {
appState.theme = appState.theme === "light" ? "dark" : "light";
}
export function addNotification(text: string) {
appState.notifications.push({ id: crypto.randomUUID(), text, read: false });
}
export function markAllRead() {
appState.notifications.forEach((n) => { n.read = true; });
}
export function toggleSidebar() {
appState.sidebar.open = !appState.sidebar.open;
}
Use in Components
// components/Header.tsx — Read state with useSnapshot
import { useSnapshot } from "valtio";
import { appState, toggleTheme, toggleSidebar } from "@/store/app";
export function Header() {
// useSnapshot creates a read-only snapshot
// Component ONLY re-renders when `user` or `theme` changes
// Changes to `notifications` or `sidebar` don't trigger re-render here
const snap = useSnapshot(appState);
return (
<header className="flex items-center justify-between p-4">
<button onClick={toggleSidebar}>☰</button>
<span>{snap.user?.name ?? "Guest"}</span>
<button onClick={toggleTheme}>
{snap.theme === "light" ? "🌙" : "☀️"}
</button>
</header>
);
}
// components/NotificationBell.tsx — Derived/computed values
import { useSnapshot } from "valtio";
import { appState, markAllRead } from "@/store/app";
export function NotificationBell() {
const snap = useSnapshot(appState);
const unread = snap.notifications.filter((n) => !n.read).length;
return (
<button onClick={markAllRead} className="relative">
🔔
{unread > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{unread}
</span>
)}
</button>
);
}
Computed Values with derive
// store/derived.ts — Computed values that auto-update
import { derive } from "valtio/utils";
import { appState } from "./app";
export const derived = derive({
unreadCount: (get) => get(appState).notifications.filter((n) => !n.read).length,
isDarkMode: (get) => get(appState).theme === "dark",
isLoggedIn: (get) => get(appState).user !== null,
});
Subscribe Outside React
// Listen to state changes outside components
import { subscribe } from "valtio";
import { appState } from "./store/app";
// Log every state change
subscribe(appState, () => {
console.log("State changed:", JSON.stringify(appState));
});
// Subscribe to specific property
subscribe(appState.sidebar, () => {
localStorage.setItem("sidebar-open", String(appState.sidebar.open));
});
// Use in WebSocket handler
socket.on("notification", (data) => {
appState.notifications.push(data); // Components auto-update
});
Examples
Example 1: Build a shopping cart
User prompt: "Build a shopping cart with add/remove/update quantity using simple state management."
The agent will create a Valtio proxy for cart state, actions that directly mutate the array, and components that reactively display cart contents and total.
Example 2: Theme and layout preferences
User prompt: "Store user preferences (theme, language, sidebar state) that persist across page loads."
The agent will create a Valtio store with localStorage sync via subscribe, and components that read preferences reactively.
Guidelines
proxy()for state,useSnapshot()for reading — always use snapshot in components- Mutate directly —
state.count++works; nosetStateorset()needed - Automatic render optimization — only re-renders when accessed properties change
subscribe()for side effects — persist to localStorage, log, syncderive()for computed values — auto-recalculates when dependencies change- Works outside React — mutate from event handlers, WebSocket, timers
- Snapshot is read-only — don't mutate
snap, mutate the originalproxy - Arrays work naturally —
push,splice,filterall trigger re-renders - Nested objects tracked —
state.user.name = "new"triggers re-render - Devtools —
import { devtools } from "valtio/utils"for Redux DevTools integration
Information
- Version
- 1.0.0
- Author
- terminal-skills
- Category
- Development
- License
- Apache-2.0