BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE React Interview Notes - How to Choose State Management Solutions

In-depth analysis of React state management solution selection strategies, comparing useState, useContext, Redux, Zustand, and more. Learn best practices and choose the right state management tool based on your project needs.

影片縮圖

Lazy to read articles? Then watch videos!

What is State Management? Why Do We Need Different Solutions?

State management refers to how we organize, store, and manage data in applications. In React applications, UI is a function of state: UI = f(state). As applications grow, state management complexity increases accordingly.

Applications of different scales and complexities require different state management solutions, mainly considering the following factors:

  • 1. State Scope - Local state vs Global shared state vs Atomic state
  • 2. Team Size - Small team vs Large team collaboration
  • 3. Application Complexity - Simple forms vs Complex state management

Comparison of Popular State Management Solutions

Classification by State Scope

TypeSolutionUse Cases
Local StateuseStateForm input control, toggle states, counters
useReducerShopping cart management, multi-step forms, game states
Shared StateContext APITheme switching, user authentication state, language settings
ZustandE-commerce shopping cart, todo applications, simple data dashboards
Large ApplicationsReduxEnterprise management systems, social media platforms, complex e-commerce sites
AtomicJotai/RecoilDocument editors, drawing applications, complex UIs requiring fine-grained updates

按團隊規模分類

Team SizeRecommended SolutionReason
Small Team/Personal ProjectuseState + useContextSimple and intuitive, fast development, no additional dependencies
ZustandLightweight, clean API, low learning curve
JotaiAtomic state management, flexible and easy to use
Medium to Large TeamsRedux ToolkitStandardized state management, rich middleware, powerful dev tools, suitable for team collaboration

Simple Trade-offs in State Management Solutions

Start Choosing State Management Solution

Is state used only in a single component?

Yes

Use useState / useReducer

No (Needs shared state)

Is it a large complex application?

Yes

Consider Redux

No (Atomic or global shared state)

Use Context API / Zustand / Jotai

Complexity and Feature Comparison of State Management Solutions

High Complexity
Low Complexity
Simple Features
Powerful Features
useState
useReducer
Context API
Zustand
Jotai/Recoil
Redux

State Management Solutions Quick Reference

useState: Simple component state
useReducer: Complex component state
Context: Small app shared state
Zustand: Medium app state management
Jotai/Recoil: Atomic state management
Redux: Large complex applications

Detailed Explanation of React's Built-in State Management Solutions

1. useState - Component Local State

The simplest state management method, used for storing and updating local state within components.

import { useState } from 'react';

function Counter() {
  // Declare a state variable named "count" with initial value 0
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Best Use Cases:

  • 1. Internal State Management - UI component internal state
  • 2. Complex Form State - Form validation, error handling
  • 3. Global State - Application-level shared state

Tip: When multiple useState variables have logical relationships, consider using useReducer to simplify state management.

2. useReducer - Complex Local State

When component state logic is complex, useReducer provides a more structured state management approach. It's based on Redux concepts but scoped to the component level.

import { useReducer } from 'react';

// Define reducer function
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error();
  }
}

function Counter() {
  // Initialize state and dispatch function with reducer
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  
  return (
    <div>
      <p>Current count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

Best Use Cases:

  • 1. Complex form state management
  • 2. Multiple related state variables need synchronous updates
  • 3. Components with complex state change logic
  • 4. Need centralized state transition logic management

3. Context API - Cross-Component State Sharing

Context API is used to share data between components at different levels, avoiding 'props drilling' issues. It's particularly suitable for sharing global information like themes and user authentication.

Context API Working Principle

ThemeProvider

Stores theme state

Context.Provider

theme: 'dark'

toggleTheme()

Header

No Context

Content

No Context

Footer

No Context

ThemeButton

useContext

⚠️ Re-renders when Context changes

Components that re-render when Context value changes

Components unaffected by Context changes

⚠️ Performance Considerations

Context value changes will cause all components consuming that Context to re-render, not suitable for frequently changing data.

1. Parent re-renders every time count changes

2. ChildA is a direct child of Parent

3. So even without using context, the render function is still called again

Optimization Tips:

  • Split state into multiple Contexts to reduce unnecessary re-renders
  • Use React.memo to prevent unnecessary re-renders
  • Use useMemo to cache Context values and avoid unnecessary Provider re-renders
const CounterContext = createContext();

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{ count, setCount }}>
      <ChildA />   
      <ChildB />   
    </CounterContext.Provider>
  );
}

function ChildA() {
  console.log('ChildA render');
  return <p>Hello</p>;
}

function ChildB() {
  const { count } = useContext(CounterContext);
  console.log('ChildB render');
  return <p>Count: {count}</p>;
}

// ------------------------------
const ChildA = React.memo(() => {
  console.log('ChildA render');
  return <p>Hello</p>;
});

// ------------------------------
const ThemeContext = createContext();
const UserContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value={{ theme: 'dark' }}>
      <UserContext.Provider value={{ user: 'John' }}>
        <Main />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

Best Use Cases:

  • 1. Application theme settings
  • 2. User authentication information
  • 3. Language/internationalization configuration
  • 4. Global state for small to medium applications

Note: Context is not suitable for frequently changing data, because the change of Context value will cause all components that consume the Context to re-render. You can combine useMemo, memo, etc. to optimize performance.

Detailed Explanation of Third-Party State Management Libraries

1. Redux - Mature Centralized State Management

Redux is the most mature state management library in the React ecosystem, based on the principles of a single data source and immutable data, making state changes predictable and traceable.

Redux Workflow Diagram

UI

Action (State Change)

Store (Global Shared State)

Reducer (State Change Logic)

Unidirectional Data Flow: UI → Action → Reducer → Store → UI

// Create slice (Redux Toolkit)
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => {
      state.value += 1;
    },
    decrement: state => {
      state.value -= 1;
    }
  }
});

// Export actions
export const { increment, decrement } = counterSlice.actions;

// Configure store
const store = configureStore({
  reducer: {
    counter: counterSlice.reducer
  }
});

// Use in React component
import { useDispatch, useSelector } from 'react-redux';
import { increment, decrement } from './counterSlice';

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>
  );
}

優勢:

  • 1. High state predictability, easy to debug
  • 2. Powerful development tools support time-travel debugging
  • 3. Middleware system extends functionality (async operations etc.)
  • 4. Mature ecosystem and best practices

適用場景:

  • 1. Large e-commerce platforms (like Shopee, PChome) need to manage complex states like products, shopping carts, users, orders, etc.
  • 2. Enterprise management systems (like ERP, CRM systems) handle large amounts of forms and data flow
  • 3. Social media applications (like Facebook, Instagram clones) manage user interactions, notifications, and content flow
  • 4. Financial trading platforms need strict state tracking and audit functionality

2. Zustand - Simple and Easy-to-Use State Management

Zustand is a small and easy-to-use state management library that provides Redux-like functionality but with a cleaner API and less boilerplate code.

Zustand Working Principle

Zustand Store
Single State Source

state

{ count: 0 }

actions

increment()

decrement()

Component A

useStore()

Only subscribes to count

Component B

useStore()

Only subscribes to increment

Component C

useStore()

Subscribes to entire store

Central storage for all states and operations

Components can selectively subscribe to needed states, only re-rendering when subscribed states change

No Provider wrapper needed, direct store access

Zustand Advantages:

  • 1. Clean API, almost no boilerplate code
  • 2. Small package size (about 3KB)
  • 3. No Provider wrapper needed
  • 4. Based on hooks, integrates well with React
  • 5. Supports selective subscription, built-in performance optimization

Use Cases:

  • 1. Personal blogs or portfolio websites that need simple global state but don't want Redux's complexity
  • 2. Small to medium e-commerce websites (like small brand websites) managing shopping carts and user preferences
  • 3. Project management tools (like Trello clones) managing boards and task states
  • 4. MVP products for startups, need rapid development while keeping code clean

3. Jotai/Recoil - Atomic State Management

Jotai and Recoil provide atomic state management, breaking down state into small, independent atomic units that can be composed and derived, suitable for performance-focused applications.

Atomic State Management and Component Relationship Diagram

Atomic state management breaks down state into independent small units (atoms), multiple components can share the same atom:

Atom Layer (Atoms)

Count Atom

Value: 0

Theme Atom

Value: 'dark'

User Atom

Value: { name: 'User' }

Component Layer (Components)

Counter Component

Uses count atom

Uses theme atom

Settings Component

Uses theme atom

Uses user atom

User Profile Component

Uses user atom

Rendering Behavior During State Updates

Count Atom Update

Value: 0 → 1

Theme Atom Update

Value: 'dark' → 'light'

User Atom Update

Value: Update username

Counter Component

✓ Re-renders

Settings Component

✗ No render

User Profile Component

✗ No render

Counter Component

✓ Re-renders

Settings Component

✓ Re-renders

User Profile Component

✗ No render

Counter Component

✗ No render

Settings Component

✓ Re-renders

User Profile Component

✓ Re-renders

Precise rendering in atomic state management: Only components subscribed to specific atoms will re-render when those atoms update

Advantages of Atomic State Management:

  • 1. Precise updates: Only components subscribed to specific atoms will re-render, avoiding unnecessary renders
  • 2. Shared state: Multiple components can share the same atom without prop drilling
  • 3. State isolation: Unrelated state changes won't trigger unnecessary re-renders
  • 4. Composable state: Can derive complex states from basic atoms
  • 5. Testability: Each atom can be tested independently without mocking the entire state tree

Compared to traditional global state management, atomic state management is more flexible and precise, especially suitable for complex applications with frequent UI state changes.

// Jotai example
import { atom, useAtom } from 'jotai';

// Create atomic states
const countAtom = atom(0);
const doubleCountAtom = atom(get => get(countAtom) * 2);

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double count: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

優勢:

  • 1. Fine-grained re-render control, performance optimization
  • 2. Composable state fragments
  • 3. Derived state and async support
  • 4. Code splitting friendly
  • 5. Atomic-level state for easy testing and maintenance

適用場景:

  • 1. Data visualization dashboards (like Google Analytics clones) that need efficient updates of multiple independent charts
  • 2. Collaborative editing tools (like Google Docs or Notion clones) that need precise UI update control
  • 3. Real-time chat applications (like Slack or Discord clones) that need to handle multiple chat rooms and notification states
  • 4. Stock trading or cryptocurrency monitoring applications that need high-frequency updates of multiple independent data points

🔥 Common Interview Questions

(1) What are the pros and cons of different React state management solutions? How to choose the right one?

Answer: Let's briefly compare various React state management solutions:

Comparison of State Management Solutions

useState/useReducer

✅ Simple and intuitive, built-in feature

⛔️ Limited to component or requires lifting state

Use for: Forms, counters, and simple UIs

Context API

✅ Avoids props drilling, built-in feature

⛔️ All consumers re-render, performance issues

Use for: Theme, user authentication, low-frequency updates

Redux

✅ Centralized management, high predictability, powerful tools

⛔️ Lots of boilerplate, steep learning curve

// Typical Redux flow dispatch(action) → reducer → store update → UI re-render

Zustand

✅ Simple API, small bundle, good performance

⛔️ Ecosystem not as mature as Redux

// Zustand simple API const useStore = create(set => ({ count: 0, increment: () => set(state => ({count: state.count + 1})) }))

Jotai/Recoil

✅ Atomic state, fine-grained updates

⛔️ Relatively new, fewer best practices

// Jotai atomic state const countAtom = atom(0) const doubleAtom = atom(get => get(countAtom) * 2)

How to choose? Consider these questions:

  • 1Application scale? Small scale use useState/Context, medium scale consider Zustand/Jotai, large scale consider Redux.
  • 2State complexity? Simple values use useState or useReducer, complex objects use third-party libraries
  • 3Update frequency? High-frequency updates avoid using Context (affects everything), consider Zustand/Jotai
  • 4Team familiarity? Choose technologies familiar to the team to reduce learning costs
  • 5Debugging needs? Need complex global state sharing choose Redux

Interview tip: Don't just say "X is the best"—show that you understand the trade-offs of each solution and can choose the right tool for the specific need.