Going Deeper: Production ReactPro· 50 min read

Global State: Redux Toolkit & Zustand

Manage app-wide state — cart, user, theme — with the two libraries real React jobs use.

What you will learn

  • Explain when you need a global state library
  • Set up a slice and store with Redux Toolkit
  • Compare it with the lighter Zustand

When context is not enough

You learned useContext for sharing data without prop drilling. For many apps that is plenty. But as an app grows — a shopping cart touched from five screens, a logged-in user read everywhere, lots of frequent updates — teams reach for a dedicated state-management library. These give you one organised place for app-wide state, predictable update rules, and great debugging tools. When a job ad lists “state management”, this is what they mean.

Two libraries dominate: Redux Toolkit (the official, batteries-included version of Redux — the long-time industry standard) and Zustand (a newer, much lighter option). We will set up the same idea — a counter shared across the app — in each, so you can see the difference. Both are real project code, shown with Output.

Redux Toolkit: slice + store

Redux organises state into slices. A slice bundles together one chunk of state and the functions that change it (called reducers). You create a slice, put it in the store (the single object that holds all global state), then read and update it from components.

Install Redux Toolkit and the React bindings
npm install @reduxjs/toolkit react-redux
A counter slice: state + the rules that change it
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => { state.value += 1; },   // looks like mutation, but it is safe here
    decrement: (state) => { state.value -= 1; },
    reset:     (state) => { state.value = 0; }
  }
});

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;

The slice defines initialState (the counter starts at 0) and three reducersincrement, decrement, reset — each describing how the state changes. Writing state.value += 1 looks like you are mutating state, but Redux Toolkit lets you do that safely and turns it into a proper immutable update behind the scenes. The slice exports actions (the functions components call) and a reducer (which goes into the store).

The store holds every slice; a Provider shares it
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice.js';

export const store = configureStore({
  reducer: { counter: counterReducer }
});

// main.jsx — wrap the app once with <Provider store={store}>

The store combines all your slices’ reducers into one global state object. Just like context and React Query, you wrap the whole app once in <Provider store={store}> so any component can reach the store.

Read with useSelector, change with dispatch
// Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './counterSlice.js';

function Counter() {
  const value = useSelector((state) => state.counter.value);  // read
  const dispatch = useDispatch();                              // get the dispatcher

  return (
    <div>
      <h2>Count: {value}</h2>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

Two hooks connect a component to the store:

  1. Read with useSelector: useSelector((state) => state.counter.value) picks the exact piece of global state this component needs. When that value changes, only this component re-renders.
  2. Change with useDispatch: const dispatch = useDispatch() gives you the dispatcher, and dispatch(increment()) sends the increment action to the store, which runs the matching reducer and updates the count.

Note: Output: Count: 0 with + and - buttons. Any component anywhere in the app that reads state.counter.value shows the same number, and clicking the buttons updates all of them at once — that is the point of global state.

Zustand: the lightweight alternative

Many modern teams prefer Zustand because it needs far less setup — no slices, no provider, no boilerplate. You create a store as a single hook and use it directly.

Install Zustand
npm install zustand
The whole store in a few lines — no provider needed
// useCounterStore.js
import { create } from 'zustand';

const useCounterStore = create((set) => ({
  value: 0,
  increment: () => set((s) => ({ value: s.value + 1 })),
  reset:     () => set({ value: 0 })
}));

// Counter.jsx
function Counter() {
  const value = useCounterStore((s) => s.value);
  const increment = useCounterStore((s) => s.increment);
  return <button onClick={increment}>Count: {value}</button>;
}

With Zustand, create((set) => ({ ... })) defines the state (value) and the functions that change it (increment, reset) all in one place; set updates the store. Components read straight from the hook — useCounterStore((s) => s.value) — with no provider to wrap and no separate slice/store files. That brevity is why it is increasingly popular for small-to-medium apps.

Redux ToolkitZustand
Setup / boilerplateMore (slices, store, Provider)Very little
Provider neededYes (<Provider>)No
DevTools & ecosystemExcellent, industry standardGood, lighter
Best forLarge/complex apps & teamsSmall-to-medium apps

Tip: Reach for a global store only when state is genuinely shared across many distant parts of the app. For data that comes from a server, prefer React Query — that is server state, not the kind of client state these libraries are best at.

Watch out: Do not put everything in global state. Keep local UI state (a form field, an open/closed menu) in useState. Global state is for data many unrelated components must share.

Q. In Redux Toolkit, how does a component read a value from the store?

Answer: useSelector reads a chosen slice of the store and re-renders the component when that value changes; useDispatch sends actions to change it.

✍️ Practice

  1. Build a Redux Toolkit counter slice and read/update it from two separate components.
  2. Rebuild the same counter with Zustand and note how much less setup it took.

🏠 Homework

  1. Add a “theme” (light/dark) to a global store (Redux Toolkit or Zustand) and read it from two components. Write two sentences on when you would choose a global store over useContext.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →