Learn ReactJS - Modern State Management
Episode 10 of 24

Learn ReactJS - Modern State Management

This episode covers the Context API for shared state, useReducer for complex state logic, then Zustand and Redux Toolkit as modern state managers. You'll also learn best practices for global state versus local state to keep your architecture healthy.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Props flowing five levels down — what's called prop drilling — makes code hard to maintain. Episode 10 introduces the solution: sharing state between components without piercing through every layer.

We start with React's built-in Context API and useReducer for complex state logic, then look at two state managers widely used in production: Zustand and Redux Toolkit. Finally, the global state versus local state rules that shape your project's architecture.

Context API for Shared State

Creating and Consuming Context

Context provides a value to an entire subtree without chained props. Three steps: create the context, provide the value, then consume it:

JSContext for theme
import { createContext, useContext, useState } from "react"
 
const TemaContext = createContext("terang")
 
function Tombol() {
  const tema = useContext(TemaContext)
  return <button className={`tombol-${tema}`}>Klik</button>
}
 
function App() {
  const [tema, setTema] = useState("terang")
  return (
    <TemaContext.Provider value={tema}>
      <Tombol />
      <button onClick={() => setTema(tema === "terang" ? "gelap" : "terang")}>
        Ganti Tema
      </button>
    </TemaContext.Provider>
  )
}

const tema = useContext(TemaContext) reads the context value in any component inside <TemaContext.Provider>. All components consuming the context re-render when the provider's value changes.

When Context Is Enough

Context fits best for values that change rarely and are read in many places: theme, language, or user identity. For fast-changing, complex state, context alone is inconvenient — that's where state managers come in.

useReducer for Complex State Logic

A Reducer Replaces Many useState Calls

useReducer manages state whose transitions are complex. All updates are expressed as actions processed by a single function:

JSuseReducer for a cart
import { useReducer } from "react"
 
function reducer(state, action) {
  switch (action.type) {
    case "tambah":
      return { ...state, total: state.total + action.nilai }
    case "reset":
      return { total: 0 }
    default:
      return state
  }
}
 
function Keranjang() {
  const [state, dispatch] = useReducer(reducer, { total: 0 })
  return (
    <div>
      <p>Total: {state.total}</p>
      <button onClick={() => dispatch({ type: "tambah", nilai: 5 })}>+5</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  )
}

dispatch({ type: "tambah", nilai: 5 }) sends an action to the reducer function, which returns the new state. Update logic is centralized and easy to test, not scattered across many handlers.

Zustand, Redux Toolkit, or Recoil

Zustand: Lightweight and Boilerplate-Free

Zustand is popular because its API is small and it doesn't need a provider at the top level:

Install Zustand
npm install zustand
JSZustand store
import { create } from "zustand"
 
const useKeranjang = create((set) => ({
  total: 0,
  tambah: (nilai) => set((state) => ({ total: state.total + nilai })),
  reset: () => set({ total: 0 }),
}))
 
function TombolTambah() {
  const tambah = useKeranjang((state) => state.tambah)
  return <button onClick={() => tambah(5)}>+5</button>
}

create((set) => ({ ... })) builds a store with state and actions together. Components just call useKeranjang((state) => state.tambah) to use an action — no Provider, no separate reducer.

Redux Toolkit: Structured for Large Teams

Redux Toolkit is the modern version of Redux with createSlice, which unifies state, reducer, and actions:

Install Redux Toolkit
npm install @reduxjs/toolkit react-redux
JSRedux Toolkit slice
import { createSlice, configureStore } from "@reduxjs/toolkit"
 
const keranjangSlice = createSlice({
  name: "keranjang",
  initialState: { total: 0 },
  reducers: {
    tambah(state, action) { state.total += action.payload },
    reset(state) { state.total = 0 },
  },
})
 
const store = configureStore({ reducer: keranjangSlice.reducer })

createSlice creates the reducer and action creators at once. Redux Toolkit suits large teams with many developers because its structure is explicit and it uses Redux DevTools for debugging.

Recoil

Recoil offers atoms and selectors centered on the data. Choosing between libraries really depends on your needs: Zustand for conciseness, Redux Toolkit for team scale, Recoil for atom patterns.

Best Practices: Global State vs Local State

The Golden Rule

Don't put all state globally. The simple rule:

  • Local state for data used by just one component or small in scope: input text, toggles, active tab.
  • Global state for data read by many components across different trees: logged-in user, cart, theme, notifications.

Start Small

Start with useState, move up to Context or useReducer when prop drilling gets annoying, and only use a global state manager when it's genuinely needed. Many apps never need Redux — don't install it because it's trendy.

Set up a state library in your project
npm install zustand
npm run dev

Practice: move the theme state from the Context example above into a Zustand store, then compare the code structure. Both are valid — choose whichever is most comfortable for your case.

Conclusion

Episode 10 resolved the state management dilemma: the Context API for simple shared state, useReducer for complex logic, Zustand and Redux Toolkit as modern state managers, plus the rules for global versus local state.

Key takeaways:

  • Context shares a value across a whole subtree without prop drilling.
  • useReducer routes all updates through actions and a single reducer.
  • Zustand is lightweight without a Provider; Redux Toolkit is structured for large teams.
  • Recoil uses atoms and selectors for centralized state.
  • Local state first, then global state when prop drilling gets in the way.
  • State manager libraries are chosen based on need, not trends.

In the next episode, episode 11, we'll cover forms & validation — controlled and uncontrolled components, React Hook Form with validation rules, schema validation with Yup or Zod, and form accessibility and input feedback. Forms are the gateway for data from users.

Learn ReactJS - Modern State Management | Learn ReactJS