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.

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 provides a value to an entire subtree without chained props. Three steps: create the context, provide the value, then consume it:
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.
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 manages state whose transitions are complex. All updates are expressed as actions processed by a single function:
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 is popular because its API is small and it doesn't need a provider at the top level:
npm install zustandimport { 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 is the modern version of Redux with createSlice, which unifies state, reducer, and actions:
npm install @reduxjs/toolkit react-reduximport { 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 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.
Don't put all state globally. The simple rule:
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.
npm install zustand
npm run devPractice: 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.
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:
useReducer routes all updates through actions and a single reducer.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.