Claude Code for Valtio: Proxy-Based State Management — Claude Skills 360 Blog
Blog / Frontend / Claude Code for Valtio: Proxy-Based State Management
Frontend

Claude Code for Valtio: Proxy-Based State Management

Published: April 8, 2027
Read time: 6 min read
By: Claude Skills 360

Valtio uses JavaScript Proxy to make plain objects reactive — const state = proxy({ count: 0 }) creates a reactive store. useSnapshot(state) returns an immutable snapshot for React rendering — components re-render only when accessed properties change. Mutate state directly: state.count++ or state.user = newUser. subscribe(state, () => console.log(snapshot(state))) watches changes outside React. derive({ doubled: get => get(state).count * 2 }) creates computed values. proxyWithComputed(initial, { computed }) colocates state and computed in one object. devtools(state) connects to Redux DevTools. proxySet() and proxyMap() provide reactive Set and Map. Valtio works standalone without React using snapshot(state) and subscribe. Claude Code generates Valtio stores, async action patterns, derived state, and component subscription patterns for minimal-boilerplate React state.

CLAUDE.md for Valtio

## Valtio Stack
- Version: valtio >= 1.13
- Store: const state = proxy({ count: 0, user: null as User | null })
- Read: const snap = useSnapshot(state) — reactive, snap.count triggers re-render
- Write: state.count++ / state.user = user — direct mutation outside React
- Computed: derive({ total: get => get(cartState).items.reduce(...) })
- Subscribe: subscribe(state, () => { /* snap = snapshot(state) */ }) — external
- DevTools: import { devtools } from "valtio/utils"; devtools(state, { name: "CartStore" })
- Collections: proxySet<string>() / proxyMap<string, Item>() — reactive Set/Map
- Snapshot: snapshot(state) — get plain object outside React

Store Setup

// stores/cart-store.ts — Valtio store with actions
import { proxy, subscribe, snapshot } from "valtio"
import { derive } from "valtio/utils"
import { devtools } from "valtio/utils"

export type CartItem = {
  id: string
  productId: string
  name: string
  priceCents: number
  quantity: number
  imageUrl: string
}

type CartState = {
  items: CartItem[]
  couponCode: string | null
  discountCents: number
  isLoading: boolean
  error: string | null
}

export const cartState = proxy<CartState>({
  items: [],
  couponCode: null,
  discountCents: 0,
  isLoading: false,
  error: null,
})

// Derived state — computed from cartState
export const cartDerived = derive({
  subtotalCents: get => get(cartState).items.reduce(
    (sum, item) => sum + item.priceCents * item.quantity,
    0,
  ),
  totalCents: get => {
    const { items, discountCents } = get(cartState)
    const subtotal = items.reduce((sum, item) => sum + item.priceCents * item.quantity, 0)
    return Math.max(0, subtotal - discountCents)
  },
  itemCount: get => get(cartState).items.reduce((sum, item) => sum + item.quantity, 0),
  isEmpty: get => get(cartState).items.length === 0,
})

// Actions — plain functions that mutate proxy
export function addItem(product: { id: string; name: string; priceCents: number; imageUrl: string }) {
  const existing = cartState.items.find(i => i.productId === product.id)
  if (existing) {
    existing.quantity++
  } else {
    cartState.items.push({
      id: crypto.randomUUID(),
      productId: product.id,
      name: product.name,
      priceCents: product.priceCents,
      quantity: 1,
      imageUrl: product.imageUrl,
    })
  }
}

export function removeItem(itemId: string) {
  const index = cartState.items.findIndex(i => i.id === itemId)
  if (index >= 0) cartState.items.splice(index, 1)
}

export function updateQuantity(itemId: string, quantity: number) {
  const item = cartState.items.find(i => i.id === itemId)
  if (!item) return
  if (quantity <= 0) {
    removeItem(itemId)
  } else {
    item.quantity = quantity
  }
}

export function clearCart() {
  cartState.items = []
  cartState.couponCode = null
  cartState.discountCents = 0
}

// Async action — mutate state before/after async work
export async function applyCoupon(code: string) {
  cartState.isLoading = true
  cartState.error = null

  try {
    const res = await fetch(`/api/coupons/${code}`)
    if (!res.ok) throw new Error("Invalid coupon code")
    const { discountCents } = await res.json()
    cartState.couponCode = code
    cartState.discountCents = discountCents
  } catch (err) {
    cartState.error = err instanceof Error ? err.message : "Failed to apply coupon"
  } finally {
    cartState.isLoading = false
  }
}

// Connect Redux DevTools in development
if (process.env.NODE_ENV === "development") {
  devtools(cartState, { name: "CartStore", enabled: true })
}

// Persist cart to localStorage
subscribe(cartState, () => {
  const snap = snapshot(cartState)
  localStorage.setItem("cart", JSON.stringify({ items: snap.items, couponCode: snap.couponCode }))
})

// Hydrate from localStorage
function hydrateCart() {
  try {
    const stored = localStorage.getItem("cart")
    if (stored) {
      const { items, couponCode } = JSON.parse(stored)
      cartState.items = items ?? []
      cartState.couponCode = couponCode ?? null
    }
  } catch {
    // Ignore parse errors
  }
}

if (typeof window !== "undefined") hydrateCart()

React Components

// components/Cart.tsx — useSnapshot for reactive reads
"use client"
import { useSnapshot } from "valtio"
import { cartState, cartDerived, addItem, removeItem, updateQuantity, clearCart, applyCoupon } from "@/stores/cart-store"
import { useState } from "react"

export function CartSummary() {
  // useSnapshot returns immutable snapshot — only re-renders on accessed fields
  const cart = useSnapshot(cartState)
  const derived = useSnapshot(cartDerived)

  if (derived.isEmpty) {
    return <p className="text-muted-foreground">Your cart is empty</p>
  }

  return (
    <div className="space-y-3">
      <p className="text-sm text-muted-foreground">{derived.itemCount} items</p>

      <div className="space-y-2">
        {cart.items.map(item => (
          <CartItem key={item.id} item={item} />
        ))}
      </div>

      <div className="border-t pt-3 space-y-1">
        <div className="flex justify-between text-sm">
          <span>Subtotal</span>
          <span>${(derived.subtotalCents / 100).toFixed(2)}</span>
        </div>
        {cart.discountCents > 0 && (
          <div className="flex justify-between text-sm text-green-600">
            <span>Discount ({cart.couponCode})</span>
            <span>-${(cart.discountCents / 100).toFixed(2)}</span>
          </div>
        )}
        <div className="flex justify-between font-semibold">
          <span>Total</span>
          <span>${(derived.totalCents / 100).toFixed(2)}</span>
        </div>
      </div>

      <button onClick={clearCart} className="text-sm text-red-500 hover:underline">
        Clear cart
      </button>
    </div>
  )
}

// Fine-grained subscription — only re-renders when this item's data changes
function CartItem({ item }: { item: { id: string; name: string; priceCents: number; quantity: number } }) {
  return (
    <div className="flex items-center gap-3">
      <div className="flex-1">
        <p className="text-sm font-medium">{item.name}</p>
        <p className="text-xs text-muted-foreground">${(item.priceCents / 100).toFixed(2)} each</p>
      </div>
      <div className="flex items-center gap-2">
        <button
          onClick={() => updateQuantity(item.id, item.quantity - 1)}
          className="size-6 rounded border text-sm"
        >

        </button>
        <span className="w-8 text-center text-sm">{item.quantity}</span>
        <button
          onClick={() => updateQuantity(item.id, item.quantity + 1)}
          className="size-6 rounded border text-sm"
        >
          +
        </button>
      </div>
      <button onClick={() => removeItem(item.id)} className="text-xs text-red-500">
        Remove
      </button>
    </div>
  )
}

export function CouponInput() {
  const cart = useSnapshot(cartState)
  const [code, setCode] = useState("")

  if (cart.couponCode) {
    return (
      <div className="flex items-center gap-2 text-sm text-green-600">
        <span>Coupon applied: {cart.couponCode}</span>
      </div>
    )
  }

  return (
    <div className="flex gap-2">
      <input
        value={code}
        onChange={e => setCode(e.target.value)}
        placeholder="Coupon code"
        className="input flex-1"
      />
      <button
        onClick={() => applyCoupon(code)}
        disabled={cart.isLoading || !code}
        className="btn-secondary"
      >
        {cart.isLoading ? "Applying..." : "Apply"}
      </button>
      {cart.error && <p className="text-sm text-red-500">{cart.error}</p>}
    </div>
  )
}

proxySet and proxyMap

// stores/selection-store.ts — reactive Set and Map
import { proxy } from "valtio"
import { proxySet, proxyMap } from "valtio/utils"

type SelectionState = {
  selectedIds: ReturnType<typeof proxySet<string>>
  loadingIds: ReturnType<typeof proxySet<string>>
  cache: ReturnType<typeof proxyMap<string, { data: unknown; timestamp: number }>>
}

export const selectionState = proxy<SelectionState>({
  selectedIds: proxySet<string>(),
  loadingIds: proxySet<string>(),
  cache: proxyMap<string, { data: unknown; timestamp: number }>(),
})

export function toggleSelection(id: string) {
  if (selectionState.selectedIds.has(id)) {
    selectionState.selectedIds.delete(id)
  } else {
    selectionState.selectedIds.add(id)
  }
}

export function selectAll(ids: string[]) {
  ids.forEach(id => selectionState.selectedIds.add(id))
}

export function clearSelection() {
  selectionState.selectedIds.clear()
}

// Component usage
function SelectableList({ items }: { items: { id: string; name: string }[] }) {
  const sel = useSnapshot(selectionState)

  return (
    <ul>
      {items.map(item => (
        <li
          key={item.id}
          onClick={() => toggleSelection(item.id)}
          className={sel.selectedIds.has(item.id) ? "bg-blue-50" : ""}
        >
          {item.name}
        </li>
      ))}
    </ul>
  )
}

For the Zustand alternative when a slightly more structured store API with explicit set/get functions, middleware (immer, devtools, persist), and no Proxy magic is preferred — Zustand is more predictable for large teams and has first-class middleware support without runtime Proxy behavior, see the Zustand Advanced guide. For the Jotai alternative when atom-based granular state — where each piece of state is a separate atom that components subscribe to individually — is preferred over a single proxy object, Jotai avoids global stores entirely and integrates well with React Suspense and async atoms, see the Jotai guide. The Claude Skills 360 bundle includes Valtio skill sets covering proxy stores, derived state, and persistence patterns. Start with the free tier to try reactive state generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free