Local vs Global State
Local vs Global State
Understanding when to use local versus global state is crucial for maintainable applications.
Local State
State that belongs to a single component and doesn't need to be shared:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
Lifted State
State moved to a parent when siblings need it:
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const fahrenheit = (celsius * 9/5) + 32;
return (
<div>
<TemperatureInput
scale="celsius"
value={celsius}
onChange={setCelsius}
/>
<TemperatureInput
scale="fahrenheit"
value={fahrenheit}
onChange={(f) => setCelsius((f - 32) * 5/9)}
/>
</div>
);
}
Global State
State shared across many components at different levels:
// Theme, User Authentication, Shopping Cart, UI State
const AppContext = createContext();
function AppProvider({ children }) {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
return (
<AppContext.Provider value={{ theme, setTheme, user, setUser }}>
{children}
</AppContext.Provider>
);
}
When to Use Each
- Local State: Form inputs, toggle states, hover effects
- Lifted State: Sibling coordination, simple shared state
- Global State: Auth, theme, shopping cart, notifications
Context vs Redux
Context vs Redux
React Context
Built-in solution for sharing state across components:
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
checkAuth().then(setUser).finally(() => setLoading(false));
}, []);
const login = async (credentials) => {
const user = await loginUser(credentials);
setUser(user);
};
const logout = async () => {
await logoutUser();
setUser(null);
};
if (loading) return <Spinner />;
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// Custom hook for consuming
function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
Context Pros:
- Built into React
- No additional dependencies
- Simple API
Context Cons:
- Causes re-renders for all consumers when value changes
- No middleware support
- No dev tools integration
Redux
Predictable state container with powerful tooling:
// Slice
const authSlice = createSlice({
name: 'auth',
initialState: { user: null, loading: false, error: null },
reducers: {
loginStart: (state) => {
state.loading = true;
state.error = null;
},
loginSuccess: (state, action) => {
state.loading = false;
state.user = action.payload;
},
loginFailure: (state, action) => {
state.loading = false;
state.error = action.payload;
},
logout: (state) => {
state.user = null;
},
},
});
// Async thunk
const login = createAsyncThunk(
'auth/login',
async (credentials, { rejectWithValue }) => {
try {
return await loginUser(credentials);
} catch (error) {
return rejectWithValue(error.message);
}
}
);
// Component
function LoginForm() {
const dispatch = useDispatch();
const { loading, error } = useSelector(state => state.auth);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(login({ email, password }));
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<button disabled={loading}>Login</button>
</form>
);
}
Redux Pros:
- Predictable state updates
- Excellent dev tools
- Middleware support
- Selective re-renders
Redux Cons:
- More boilerplate
- Learning curve
- Additional bundle size
State Management Libraries
State Management Libraries
Zustand
Minimal, fast state management:
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(
devtools(
persist(
(set, get) => ({
// State
bears: 0,
users: [],
// Actions
increasePopulation: () => set((state) => ({ bears: state.bears + 1 }))
removeAllBears: () => set({ bears: 0 })
updateBears: (newBears) => set({ bears: newBears })
fetchUsers: async () => {
const users = await fetch('/api/users').then(r => r.json());
set({ users });
},
}),
{ name: 'bear-storage' }
)
)
);
// Usage
function BearCounter() {
const bears = useStore((state) => state.bears);
const increase = useStore((state) => state.increasePopulation);
return (
<div>
<span>{bears}</span>
<button onClick={increase}>Add Bear</button>
</div>
);
}
Jotai
Atomic state management:
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
const incrementAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) + 1);
});
function Counter() {
const [count] = useAtom(countAtom);
const [doubleCount] = useAtom(doubleCountAtom);
const [, increment] = useAtom(incrementAtom);
return (
<div>
<p>Count: {count}</p>
<p>Double: {doubleCount}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
Recoil
Atom-based state management:
import { atom, useRecoilState, selector } from 'recoil';
const todoListState = atom({
key: 'todoListState',
default: [],
});
const todoListStatsState = selector({
key: 'todoListStatsState',
get: ({ get }) => {
const todoList = get(todoListState);
const totalNum = todoList.length;
const totalCompletedNum = todoList.filter(item => item.isComplete).length;
const totalUncompletedNum = totalNum - totalCompletedNum;
const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;
return { totalNum, totalCompletedNum, totalUncompletedNum, percentCompleted };
},
});
function Stats() {
const { totalNum, percentCompleted } = useRecoilValue(todoListStatsState);
return (
<div>
Total: {totalNum} | Completed: {Math.round(percentCompleted * 100)}%
</div>
);
}
Choosing the Right Tool
- Local state only: useState/useReducer
- Simple global state: Zustand
- Atomic updates: Jotai or Recoil
- Complex app with middleware: Redux Toolkit
- Server state: React Query or SWR
Practice Problems
Create a reusable React component implementing State Management. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for State Management using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize State Management for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. When should you use local state instead of global state?
2. What is a major drawback of using React Context for complex state management?
3. Which library provides excellent dev tools for state inspection?
4. What is Zustand's main advantage?
5. When should you use React Query or SWR instead of Redux?
Flashcards
Question
What is the main difference between local and global state?
Click to reveal answer
Answer
Local state is owned by a single component; global state is shared across many components.
Question
When should you choose Context over Redux?
Click to reveal answer
Answer
For simple, infrequently changing global values like theme or auth status where re-renders aren't a concern.
Question
What is Zustand?
Click to reveal answer
Answer
A minimal, fast state management library with a simple API and no boilerplate.
Question
What is the atomic state model?
Click to reveal answer
Answer
Breaking state into small, independent atoms that can be composed and updated individually.
Question
What is State Management?
Click to reveal answer
Answer
State Management is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Use local state when data is only needed in one component
- 2.Context is good for simple global state but causes re-renders
- 3.Redux provides predictable updates, middleware, and dev tools
- 4.Zustand offers minimal boilerplate for most state needs
- 5.Server state should be managed with React Query or SWR
Interview Tips
- •Explain the trade-offs between Context and Redux
- •Discuss when you would choose Zustand over Redux
- •Know the performance implications of Context re-renders
Cheat Sheet
State Management Cheat Sheet
State Types
- Local: useState, useReducer
- Lifted: Props to parent
- Global: Context, Redux, Zustand, Jotai
- Server: React Query, SWR
Context Best Practices
- Split into multiple providers by concern
- Use memoization to prevent re-renders
- Don't put rapidly changing data in context
Redux Toolkit
- Use createSlice for reducers
- Use createAsyncThunk for async
- Use RTK Query for data fetching
Quick Decision
| Need | Solution |
|---|---|
| Simple shared state | Context |
| Complex app state | Redux Toolkit |
| Minimal overhead | Zustand |
| Atomic updates | Jotai/Recoil |