[{"data":1,"prerenderedAt":127},["ShallowReactive",2],{"qa-\u002Freact\u002Fhooks\u002Fusereducer":3},{"page":4,"siblings":99,"blog":124},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":89,"related":90,"seo":91,"seoDescription":92,"stem":93,"subtopic":94,"topic":95,"topicSlug":96,"updated":97,"__hash__":98},"qa\u002Freact\u002Fhooks\u002Fusereducer.md","Usereducer",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,4,"\u002Freact\u002Fhooks\u002Fusereducer",[23,28,32,36,40,44,48,52,56,60,64,69,73,77,81,85],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-usereducer","easy","What does useReducer return?","`useReducer` returns a pair: the **current state** and a **dispatch function**.\nYou dispatch action objects to the reducer, which computes and returns the next\nstate. React then re-renders with that new state.\n\n```jsx\nconst [state, dispatch] = useReducer(reducer, initialState)\ndispatch({ type: 'increment' })\n```\n\nThe `reducer` is a pure function `(state, action) => nextState`. The\n`initialState` is used on the first render. This pattern mirrors the way Redux\nworks but lives entirely inside a single component (or shared via context).\n",{"id":29,"difficulty":25,"q":30,"a":31},"reducer-function-shape","What is the shape of a reducer function?","A reducer takes the **current state** and an **action** and returns the **next\nstate**. It must be **pure** — no side effects, no mutating the state argument,\nsame inputs always produce the same output.\n\n```jsx\nfunction counterReducer(state, action) {\n  switch (action.type) {\n    case 'increment': return { count: state.count + 1 }\n    case 'decrement': return { count: state.count - 1 }\n    case 'reset':     return { count: 0 }\n    default:          return state \u002F\u002F always handle unknown actions\n  }\n}\n```\n\nReturn the existing `state` unchanged in the `default` case — returning\n`undefined` causes subtle bugs. Always build and return a **new** object rather\nthan mutating `state`.\n",{"id":33,"difficulty":14,"q":34,"a":35},"when-usereducer","When should you prefer useReducer over useState?","Prefer `useReducer` when: (1) state has **multiple sub-fields** that update\ntogether, (2) the **next state depends on complex logic** over the previous one,\nor (3) you want to **centralize update logic** so it's easy to test without\nrendering.\n\n```jsx\n\u002F\u002F useState: scattered setters, hard to audit\nsetLoading(true); setError(null); setData(null)\n\n\u002F\u002F useReducer: one dispatch, one place to read all transitions\ndispatch({ type: 'fetch_start' })\n```\n\nFor a simple counter or toggle, `useState` is cleaner. When you find yourself\ncalling multiple setters together or your update logic is branchy, `useReducer`\nmakes intent clearer and the logic testable in isolation.\n",{"id":37,"difficulty":14,"q":38,"a":39},"init-arg","What is the third argument (initializer) of useReducer?","The optional third argument is an **initializer function**. React passes\n`initialArg` through it on the first render to compute the initial state — just\nlike lazy initialization in `useState`.\n\n```jsx\nfunction init(initialCount) {\n  return { count: initialCount }\n}\nconst [state, dispatch] = useReducer(reducer, 0, init)\n\u002F\u002F initial state: { count: 0 }\n```\n\nThe initializer is useful for computing expensive initial values, or for allowing\na \"reset to initial\" action — the reducer can call `init(action.payload)` to\nrebuild fresh initial state from a fresh argument.\n",{"id":41,"difficulty":25,"q":42,"a":43},"action-type-convention","What are action type constants and why use them?","Action types are string identifiers for what happened. Defining them as named\nconstants prevents silent typos: a misspelled string dispatches to the `default`\ncase with no error, while a misspelled constant causes a `ReferenceError`.\n\n```jsx\n\u002F\u002F prone to typos\ndispatch({ type: 'incrment' }) \u002F\u002F silently hits default\n\n\u002F\u002F constants catch typos at the source\nconst INC = 'increment'\ndispatch({ type: INC })\n```\n\nIn larger codebases, co-locate the constants with the reducer or use TypeScript\nunion types for the action shape so the compiler enforces valid combinations.\n",{"id":45,"difficulty":14,"q":46,"a":47},"sharing-dispatch","How do you share the dispatch function across components?","Combine `useReducer` with `useContext` — pass `dispatch` (and optionally `state`)\nthrough a context so any descendant can dispatch without prop drilling.\n\n```jsx\nconst StoreContext = createContext(null)\n\nfunction StoreProvider({ children }) {\n  const [state, dispatch] = useReducer(reducer, initial)\n  return (\n    \u003CStoreContext.Provider value={{ state, dispatch }}>\n      {children}\n    \u003C\u002FStoreContext.Provider>\n  )\n}\n\nfunction Counter() {\n  const { state, dispatch } = useContext(StoreContext)\n  return \u003Cbutton onClick={() => dispatch({ type: 'increment' })}>{state.count}\u003C\u002Fbutton>\n}\n```\n\n`dispatch` has stable identity across renders (React guarantees this), so putting\nit in context doesn't cause spurious re-renders.\n",{"id":49,"difficulty":14,"q":50,"a":51},"reducer-reset","How do you implement a \"reset to initial\" action in a reducer?","Handle a dedicated `reset` action type that returns the initial state. If the\ninitial state was computed by an initializer, call it from within the reducer.\n\n```jsx\nconst initialState = { count: 0, text: '' }\n\nfunction reducer(state, action) {\n  switch (action.type) {\n    case 'increment': return { ...state, count: state.count + 1 }\n    case 'reset':     return initialState  \u002F\u002F back to start\n  }\n}\ndispatch({ type: 'reset' })\n```\n\nWhen initial state is dynamic (derived from props), pass it in the action payload\nand return it: `case 'reset': return action.payload`.\n",{"id":53,"difficulty":14,"q":54,"a":55},"reducer-testing","How do you test a reducer function?","Because a reducer is a **pure function**, you test it directly — no React, no\nrendering, no mocking.\n\n```js\nimport { counterReducer } from '.\u002FcounterReducer'\n\ntest('increment adds 1', () => {\n  const next = counterReducer({ count: 5 }, { type: 'increment' })\n  expect(next.count).toBe(6)\n})\n\ntest('unknown action returns current state', () => {\n  const state = { count: 5 }\n  expect(counterReducer(state, { type: 'unknown' })).toBe(state)\n})\n```\n\nThis is one of `useReducer`'s key advantages: all your update logic is in a plain\nfunction you can cover with unit tests before wiring it up.\n",{"id":57,"difficulty":14,"q":58,"a":59},"multiple-reducers","Can you use multiple useReducers in one component?","Yes. Each call is independent and manages a separate slice of state. Use multiple\nreducers when different parts of a component's state evolve via unrelated logic.\n\n```jsx\nconst [formState, formDispatch]   = useReducer(formReducer, initialForm)\nconst [uiState,   uiDispatch]     = useReducer(uiReducer,   initialUi)\n```\n\nSplitting avoids one monolithic reducer with unrelated cases. It's similar to\nsplitting `useState` calls for independent values.\n",{"id":61,"difficulty":25,"q":62,"a":63},"action-payload","How do you pass data with a dispatch action?","Add any extra data as properties on the action object — typically called\n`payload` by convention (borrowed from Flux\u002FRedux).\n\n```jsx\ndispatch({ type: 'set_user', payload: { id: 1, name: 'Ada' } })\n\n\u002F\u002F reducer\ncase 'set_user': return { ...state, user: action.payload }\n```\n\nThe payload shape is up to you — some teams prefer `{ type, id, name }` instead\nof nesting under `payload`. Consistency matters more than the exact shape; pick\none convention and stick to it.\n",{"id":65,"difficulty":66,"q":67,"a":68},"immer-reducer","hard","How does Immer simplify reducer logic?","**Immer**'s `produce` wraps a reducer so you can *write* mutating code on a\ndraft, and it produces a correctly immutable new state behind the scenes.\n\n```jsx\nimport produce from 'immer'\n\nconst reducer = produce((draft, action) => {\n  switch (action.type) {\n    case 'add_item':\n      draft.items.push(action.item) \u002F\u002F looks like mutation, but it's safe\n      break\n    case 'update_city':\n      draft.user.address.city = action.city \u002F\u002F deep update without spreading\n      break\n  }\n})\n```\n\nThis eliminates the spreading pyramid for deeply nested state without sacrificing\nimmutability. Redux Toolkit uses Immer internally for the same reason.\n",{"id":70,"difficulty":14,"q":71,"a":72},"reducer-vs-redux","How does useReducer compare to Redux?","`useReducer` gives you the same **reducer pattern** locally, but lacks Redux's\necosystem: global store singleton, devtools time-travel, middleware for async\nlogic, and selector memoization.\n\n| | useReducer | Redux Toolkit |\n|---|---|---|\n| Scope | component \u002F context tree | entire app |\n| DevTools | none | yes |\n| Middleware | none | thunk, saga, etc. |\n| Async | manually via effects | built-in |\n\nFor local or module-level state, `useReducer` + context is often enough. For\ncross-cutting, high-churn global state, Redux Toolkit or Zustand adds more\ninfrastructure with less boilerplate.\n",{"id":74,"difficulty":14,"q":75,"a":76},"avoid-mutation","What happens if you mutate state inside a reducer?","Mutating the state object and returning it gives React the **same reference**, so\nit bails out and skips the re-render — your UI appears frozen even though the\ndata changed.\n\n```jsx\n\u002F\u002F broken: mutates the original, same reference returned\ncase 'push':\n  state.items.push(action.item) \u002F\u002F mutates!\n  return state                  \u002F\u002F same ref -> no re-render\n\n\u002F\u002F correct: new array, new object\ncase 'push':\n  return { ...state, items: [...state.items, action.item] }\n```\n\nThis is the same rule as `useState`: always return a **new** object\u002Farray for\nchanged values. Immer enforces this for you automatically.\n",{"id":78,"difficulty":66,"q":79,"a":80},"dispatch-batching","Are multiple dispatches in one event handler batched?","Yes. In React 18, all state updates — including multiple `dispatch` calls — inside\nevent handlers and async callbacks are **automatically batched** into a single\nre-render.\n\n```jsx\nfunction handleReset() {\n  dispatch({ type: 'reset_form' })\n  dispatch({ type: 'clear_errors' })\n  \u002F\u002F ONE re-render in React 18, not two\n}\n```\n\nIn React 17, only updates inside React event handlers were batched; async\ncallbacks caused separate renders per dispatch. If you need to force synchronous\nintermediate renders (rarely), use `flushSync` from `react-dom`.\n",{"id":82,"difficulty":14,"q":83,"a":84},"usereducer-no-side-effects","Why must a reducer have no side effects?","React may call the reducer **more than once** in Strict Mode (development), and\nconcurrent features can discard and replay state updates. A reducer with side\neffects (API calls, timers, random numbers, mutations) would break or run the\nside effect multiple times unpredictably.\n\n```jsx\n\u002F\u002F broken: side effect in reducer\ncase 'save':\n  fetch('\u002Fapi\u002Fsave', { body: JSON.stringify(state) }) \u002F\u002F runs on every replay\n  return state\n\n\u002F\u002F correct: reducer only computes state\ncase 'save': return { ...state, saving: true }\n\u002F\u002F then fire the request in a useEffect that watches `saving`\n```\n\nKeep reducers **pure** — dispatch + effect is the correct pattern for actions\nthat need async work alongside them.\n",{"id":86,"difficulty":14,"q":87,"a":88},"lazy-usereducer-init","When would you use the initializer function for performance?","When the initial state requires an **expensive computation** (parsing a large JSON\nblob, reading from localStorage, complex filtering), pass it as the third\nargument so it only runs once — not on every render like a value expression would.\n\n```jsx\nfunction buildInitialState(data) {\n  return { items: parse(data), selected: null } \u002F\u002F expensive parse\n}\n\nconst [state, dispatch] = useReducer(reducer, rawData, buildInitialState)\n\u002F\u002F buildInitialState(rawData) runs once on mount\n```\n\nWithout the initializer, `buildInitialState(rawData)` in the second argument\nwould still only be used once (the initialState arg is also only read once), so\nthe main win is clarity and the ability to reuse the function for reset actions.\n",16,null,{"description":11},"React useReducer interview questions — reducer functions, dispatch, action types, initializers, context integration, and when to prefer useReducer over useState.","react\u002Fhooks\u002Fusereducer","useReducer","Hooks","hooks","2026-06-23","QhWz1FylI4XYks-BksLWHTkzUOvcMTj3d_IVtz8Ntp8",[100,104,107,111,112,116,120],{"subtopic":101,"path":102,"order":103},"useState","\u002Freact\u002Fhooks\u002Fusestate",1,{"subtopic":105,"path":106,"order":12},"useEffect","\u002Freact\u002Fhooks\u002Fuseeffect",{"subtopic":108,"path":109,"order":110},"useContext","\u002Freact\u002Fhooks\u002Fusecontext",3,{"subtopic":94,"path":21,"order":20},{"subtopic":113,"path":114,"order":115},"useCallback & useMemo","\u002Freact\u002Fhooks\u002Fusecallback-usememo",5,{"subtopic":117,"path":118,"order":119},"useRef","\u002Freact\u002Fhooks\u002Fuseref",6,{"subtopic":121,"path":122,"order":123},"Custom Hooks","\u002Freact\u002Fhooks\u002Fcustom-hooks",7,{"path":125,"title":126},"\u002Fblog\u002Freact-usereducer-hook-guide","React useReducer Hook — Complete Guide for Interviews",1782244100842]