7.5 KiB
State Management & Data Patterns
Version: 1.0.0 Last Updated: 2026-03-14 Applies To: ALL applications with client state, server state, or real-time data requirements
Purpose
State management is where AI-generated applications most often break down. Without clear patterns, agents reinvent state architectures on every generation — wasting tokens and introducing inconsistency. These patterns give agents a decision tree so they can select the right approach instantly.
Core Principle: Pick the simplest state solution that works. Complexity is earned, not assumed.
State Architecture Decision Tree
Is the state used by only one component?
├─ YES → Local state (useState/signals)
├─ NO → Is it server data?
│ ├─ YES → Server state (React Query/SWR/TanStack Query)
│ └─ NO → Is it shared across many components?
│ ├─ YES → Global client state (Zustand/Jotai)
│ └─ NO → Lift state to nearest common parent
Client State Patterns
Local State (Single Component)
Use for: Form inputs, toggle states, UI-only state
// Preferred: Simple local state
function Toggle() {
const [isOpen, setIsOpen] = useState(false);
return <button onClick={() => setIsOpen(!isOpen)}>{isOpen ? 'Close' : 'Open'}</button>;
}
// Signals pattern (Leptos/Dioxus)
fn toggle() -> impl IntoView {
let (is_open, set_is_open) = create_signal(false);
view! { <button on:click=move |_| set_is_open.update(|v| *v = !*v)>
{move || if is_open() { "Close" } else { "Open" }}
</button> }
}
Global Client State (Shared Across Components)
Use for: Theme, user preferences, UI layout state, feature flags
// Zustand — preferred for global client state
import { create } from 'zustand';
interface AppState {
theme: 'light' | 'dark';
sidebarOpen: boolean;
setTheme: (theme: 'light' | 'dark') => void;
toggleSidebar: () => void;
}
const useAppStore = create<AppState>((set) => ({
theme: 'light',
sidebarOpen: true,
setTheme: (theme) => set({ theme }),
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));
// Go equivalent: typed state container
type AppState struct {
mu sync.RWMutex
Theme string
SidebarOpen bool
}
func (s *AppState) SetTheme(theme string) {
s.mu.Lock()
defer s.mu.Unlock()
s.Theme = theme
}
func (s *AppState) ToggleSidebar() {
s.mu.Lock()
defer s.mu.Unlock()
s.SidebarOpen = !s.SidebarOpen
}
Atomic State (Fine-Grained Reactivity)
Use for: Performance-critical UIs, large forms, independent reactive values
// Jotai — atomic state for fine-grained updates
import { atom, useAtom } from 'jotai';
const themeAtom = atom<'light' | 'dark'>('light');
const sidebarAtom = atom(true);
// Derived atoms for computed state
const isDarkAtom = atom((get) => get(themeAtom) === 'dark');
Server State Patterns
Data Fetching (React Query / TanStack Query)
Use for: ALL server data. Never store server responses in client state.
// React Query — the standard for server state
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function usePlayer(id: string) {
return useQuery({
queryKey: ['player', id],
queryFn: () => fetchPlayer(id),
staleTime: 30_000, // Consider fresh for 30s
gcTime: 5 * 60_000, // Garbage collect after 5min
});
}
function useUpdatePlayer() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updatePlayer,
onSuccess: (data) => {
queryClient.setQueryData(['player', data.id], data); // Optimistic update
},
});
}
Offline-First & Local Persistence
When to Use
- Mobile apps with intermittent connectivity
- Games with local save data
- Apps that should work without internet
Persistence Layer
// Zustand with persistence middleware
import { persist } from 'zustand/middleware';
const useGameStore = create(
persist<GameState>(
(set) => ({
level: 1,
score: 0,
// ...
}),
{
name: 'game-save',
storage: createJSONStorage(() => localStorage),
// NEVER persist auth tokens in localStorage
}
)
);
Real-Time & CRDT Collaboration
When to Use CRDTs
- Multiple users editing the same document
- Collaborative whiteboards or game states
- Conflict-free offline-to-online sync
// Y.js CRDT integration pattern
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://sync.example.com', 'room-id', ydoc);
const ymap = ydoc.getMap('shared-state');
// Observe changes from any peer
ymap.observe((event) => {
// Update local UI reactively
});
Forbidden Patterns
| Pattern | Why Forbidden | Use Instead |
|---|---|---|
| Global mutable singletons | Untraceable state changes, impossible to test | Zustand/Jotai stores |
| Untyped React Context for state | Type-unsafe, re-renders entire tree | Zustand with selectors |
| localStorage for auth tokens | XSS vulnerability | httpOnly cookies or secure storage |
| Storing server data in useState | Stale data, no cache invalidation | React Query / TanStack Query |
| Prop drilling > 3 levels | Maintenance nightmare | State library or composition |
| Redux for new projects | Excessive boilerplate for most apps | Zustand (simpler API, same patterns) |
HALT CONDITIONS
STOP and ask the human when:
- Auth tokens or session data need to be stored (security-critical)
- Database schema changes are required for state persistence
- Real-time sync architecture is being designed (CRDT vs OT vs custom)
- State needs to be shared across micro-frontends or services
- Offline-first strategy requires conflict resolution rules
- Performance profiling shows state updates causing frame drops
Language Patterns
TypeScript
// Type-safe state factory
function createTypedStore<T extends Record<string, unknown>>(initialState: T) {
return create<T & { reset: () => void }>((set) => ({
...initialState,
reset: () => set(initialState),
}));
}
Rust
// Type-safe state container
use std::sync::{Arc, RwLock};
pub struct AppState<T: Clone + Send + Sync> {
inner: Arc<RwLock<T>>,
}
impl<T: Clone + Send + Sync> AppState<T> {
pub fn new(initial: T) -> Self {
Self { inner: Arc::new(RwLock::new(initial)) }
}
pub fn read(&self) -> T {
self.inner.read().unwrap().clone()
}
pub fn update(&self, f: impl FnOnce(&mut T)) {
let mut state = self.inner.write().unwrap();
f(&mut state);
}
}
Go
// Type-safe state with generics
type Store[T any] struct {
mu sync.RWMutex
state T
}
func NewStore[T any](initial T) *Store[T] {
return &Store[T]{state: initial}
}
func (s *Store[T]) Get() T {
s.mu.RLock()
defer s.mu.RUnlock()
return s.state
}
func (s *Store[T]) Update(fn func(*T)) {
s.mu.Lock()
defer s.mu.Unlock()
fn(&s.state)
}
RELATED DOCUMENTS
| Document | Purpose |
|---|---|
| AI_ASSISTED_DEV.md | AI development decision matrix |
| 2026_UI_UX_STANDARD.md | UI component patterns |
| 2026_GAME_DESIGN.md | Game state requirements |
| AGENT_GUARDRAILS.md | Core safety protocols |