arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
306 lines (245 loc) • 6.47 kB
Markdown
id: arela.state_management
title: State Management
category: frontend
severity: should
version: 1.0.0
# State Management
## Principle
**Use the simplest state solution that works.** Don't reach for Redux when useState is enough.
## The Hierarchy
### **1. Local State (useState)**
```tsx
// ✅ Use for: Component-specific state
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState('');
```
**When to use:**
- State used in one component
- UI state (modals, dropdowns, forms)
- Temporary state
- No need to share
### **2. Lifted State (Props)**
```tsx
// ✅ Use for: Shared state between siblings
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<ChildA count={count} />
<ChildB setCount={setCount} />
</>
);
}
```
**When to use:**
- 2-3 components need the same state
- Parent-child relationship
- Simple data flow
### **3. Context (useContext)**
```tsx
// ✅ Use for: App-wide state (theme, auth, i18n)
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Dashboard />
</ThemeContext.Provider>
);
}
```
**When to use:**
- Truly global state (auth, theme, locale)
- Avoid props drilling >3 levels
- Infrequent updates
- Read-heavy, write-light
### **4. URL State (useSearchParams)**
```tsx
// ✅ Use for: Shareable state
const [searchParams, setSearchParams] = useSearchParams();
const filter = searchParams.get('filter') || 'all';
```
**When to use:**
- Filters, sorting, pagination
- Shareable URLs
- Browser back/forward support
- Bookmarkable state
### **5. Server State (React Query/SWR)**
```tsx
// ✅ Use for: API data
const { data, isLoading } = useQuery('users', fetchUsers);
```
**When to use:**
- Data from APIs
- Caching needed
- Background refetching
- Optimistic updates
### **6. Global State (Zustand/Redux)**
```tsx
// ✅ Use for: Complex shared state
const useStore = create((set) => ({
cart: [],
addItem: (item) => set((state) => ({
cart: [...state.cart, item]
})),
}));
```
**When to use:**
- Complex state logic
- Many components need same state
- Frequent updates
- Need devtools/time-travel
## Decision Tree
```
Where should this state live?
1. Is it used in only one component?
→ useState (local state)
2. Is it shared between 2-3 close components?
→ Lift state to common parent
3. Is it truly global (auth, theme, i18n)?
→ Context API
4. Should it be in the URL (filters, pagination)?
→ URL state (useSearchParams)
5. Is it data from an API?
→ React Query / SWR
6. Is it complex shared state with many updates?
→ Zustand / Redux
```
## Common Mistakes
### **1. Context for Everything**
```tsx
// ❌ Don't use Context for frequently changing state
const CountContext = createContext();
function App() {
const [count, setCount] = useState(0); // Re-renders entire tree!
return (
<CountContext.Provider value={{ count, setCount }}>
<Dashboard /> {/* Re-renders on every count change */}
</CountContext.Provider>
);
}
```
**Fix:** Use local state or a proper state manager.
### **2. Premature Redux**
```tsx
// ❌ Redux for simple state
const store = createStore(reducer);
// Just to store: { isModalOpen: false }
```
**Fix:** Start with useState. Add complexity only when needed.
### **3. Props Drilling Instead of Context**
```tsx
// ❌ Drilling props through 5 levels
<A user={user}>
<B user={user}>
<C user={user}>
<D user={user}>
<E user={user} /> {/* Finally uses it */}
</D>
</C>
</B>
</A>
```
**Fix:** Use Context after 3 levels.
### **4. Not Using URL State**
```tsx
// ❌ Filters in local state (not shareable)
const [filter, setFilter] = useState('all');
```
**Fix:** Put it in the URL for shareability.
## Server State is Different
**Don't store API data in useState/Redux:**
```tsx
// ❌ Manual API state management
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, []);
```
```tsx
// ✅ Use React Query
const { data: users, isLoading, error } = useQuery('users', fetchUsers);
```
**Benefits:**
- Automatic caching
- Background refetching
- Deduplication
- Optimistic updates
- Less boilerplate
## Form State
### **Simple Forms**
```tsx
// ✅ Controlled components
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
```
### **Complex Forms**
```tsx
// ✅ Use React Hook Form
const { register, handleSubmit } = useForm();
```
**Use a library when:**
- 5+ fields
- Complex validation
- Multi-step forms
- File uploads
## State Colocation
**Keep state as close to where it's used as possible:**
```tsx
// ❌ State too high
function App() {
const [modalOpen, setModalOpen] = useState(false); // Only used in Settings
return (
<Dashboard>
<Settings modalOpen={modalOpen} setModalOpen={setModalOpen} />
</Dashboard>
);
}
// ✅ State colocated
function Settings() {
const [modalOpen, setModalOpen] = useState(false); // Right where it's used
return <Modal open={modalOpen} />;
}
```
## The Zustand Sweet Spot
**When to use Zustand over Context:**
```tsx
// ✅ Zustand for complex shared state
const useCartStore = create((set) => ({
items: [],
total: 0,
addItem: (item) => set((state) => ({
items: [...state.items, item],
total: state.total + item.price,
})),
removeItem: (id) => set((state) => ({
items: state.items.filter(i => i.id !== id),
total: state.items.reduce((sum, i) => sum + i.price, 0),
})),
}));
// No Provider needed, no re-render issues
function Cart() {
const items = useCartStore(state => state.items); // Only re-renders when items change
}
```
**Benefits over Context:**
- No Provider wrapper
- Selective subscriptions (no unnecessary re-renders)
- Simpler API
- Built-in devtools
## Remember
**Start simple. Add complexity only when you feel pain.**
The best state management solution is the one you don't need yet.
*"Make it work, make it right, make it fast."*
*- Kent Beck*
*Start with "make it work" (useState), then "make it right" (proper state solution).*