Przemek Franczak
TypeScriptReactjavascriptSystem design

State in React - how to deal with it?

April 5, 2025 - 3 minutesPrzemyslaw Franczak
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

ToolUsage
useStateSimple, local state
useReducerComplex, local state
Context APISimple, shared, rarely updated state
ZustandComplex, global, frequently updated state
ReduxComplex, global state, frequently updated state of large app