State in React - how to deal with it?
1. useState - a React hook that allows adding a local state for function components
1const [state, setState] = useState(initialValue); 2
When you use:
- you need to track a simple state that relates to a single component
- you don't need to share the state between components
When not to use:
- for global/shared state like theme, chart or user
2. useReducer - a React hook that allows to manage local but complex state
1const formReducer = (state, action) => { 2 switch (action.type) { 3 case 'SET_NAME': 4 return { ...state, name: action.payload }; 5 default: 6 return state; 7 } 8}; 9 10const [state, dispatch] = useReducer(formReducer, { name: '' }); 11
When you use:
- you need to track a complex state with multiple fields that are connected, e.g. forms
- you don't need to share the state between components
- state changes are based on specific actions
When not to use:
- for global/shared state like theme, chart or user
3. Context API - a built-in feature in React that lets you share data across components without passing props manually at every level
1const ThemeContext = createContext(); 2 3const ThemeProvider = ({ children }) => { 4 const [theme, setTheme] = useState('light'); 5 return ( 6 <ThemeContext.Provider value={{ theme, setTheme }}> 7 {children} 8 </ThemeContext.Provider> 9 ); 10}; 11
When you use:
- you need to share a state between multiple components, e.g. theme
- the data in state is rarely changing
- you don't want to pass props through several levels of components (see: props drilling)
When not to use:
- the state is changing frequently
- the state is complex
Important note: Context updates cause all consuming components to re-render
4. Zustand - a lightweight alternative to Redux for managing a global state with much smaller boilerplate.
1const useUserStore = create((set) => ({ 2 user: null, 3 setUser: (user) => set({ user }), 4})); 5 6const UserProfile = () => { 7 const user = useUserStore((state) => state.user); 8 return <div>{user?.name}</div>; 9}; 10
Best features:
- no providers needed - clean API
- selectors for limit the re-renders
- based on React hooks and clousers
- persisting state to localStorage
When to use:
- you need to share the global state for the whole app
- you don't want to deal with Redux's boilerplate
- the state is changing frequently and you want to avoid re-renders
5. Redux - a library for managing global state. It gives you a centralized place to store your application’s state and a strict pattern for how to update that state.
1import { useSelector, useDispatch } from 'react-redux'; 2import type { RootState, AppDispatch } from '../store'; 3import { increment, decrement } from '../store/counterSlice'; 4 5const Counter = () => { 6 const value = useSelector((state: RootState) => state.counter.value); 7 const dispatch = useDispatch<AppDispatch>(); 8 9 return ( 10 <div> 11 <h2>Counter: {value}</h2> 12 <button onClick={() => dispatch(increment())}>+1</button> 13 <button onClick={() => dispatch(decrement())}>-1</button> 14 </div> 15 ); 16}; 17 18export default Counter; 19
When to use:
- you have a complex app with multiple modules and dependencies between
- you want to have full control: e.g, middleware, logging, devtools, undo/redo.
Summary
Tool | Usage |
---|---|
useState | Simple, local state |
useReducer | Complex, local state |
Context API | Simple, shared, rarely updated state |
Zustand | Complex, global, frequently updated state |
Redux | Complex, global state, frequently updated state of large app |