[{"data":1,"prerenderedAt":1488},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-usereducer-hook-guide":3},{"id":4,"title":5,"body":6,"description":1474,"difficulty":1475,"extension":1476,"framework":1477,"frameworkSlug":1478,"meta":1479,"navigation":586,"order":187,"path":1480,"qaPath":1481,"seo":1482,"stem":1483,"subtopic":24,"topic":1484,"topicSlug":1485,"updated":1486,"__hash__":1487},"blog\u002Fblog\u002Freact-usereducer-hook-guide.md","React useReducer Hook — Complete Guide for Interviews",{"type":7,"value":8,"toc":1461},"minimark",[9,14,31,37,41,86,115,125,129,136,301,319,323,333,386,392,475,479,484,522,604,608,618,700,707,711,725,943,952,956,959,1142,1151,1155,1166,1312,1318,1322,1405,1410,1414,1457],[10,11,13],"h2",{"id":12},"what-usereducer-is-and-why-it-exists","What useReducer is and why it exists",[15,16,17,21,22,25,26,30],"p",{},[18,19,20],"code",{},"useState"," is perfect for simple, independent values. But when a component's state has\nseveral fields that change together — a form's values plus its loading\u002Ferror\u002Fsuccess\nstatus, for example — you end up calling multiple setters in every handler and the logic\nscatters across the component. ",[18,23,24],{},"useReducer"," centralizes that logic in a single pure\nfunction: the ",[27,28,29],"strong",{},"reducer",". Every state transition is a dispatch away, and the entire\nupdate logic lives in one testable place.",[15,32,33,34,36],{},"Interviewers reach for ",[18,35,24],{}," questions when they want to know whether you can\nmodel state changes explicitly rather than imperatively calling setters.",[10,38,40],{"id":39},"the-api","The API",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-jsx shiki shiki-themes github-light github-dark","const [state, dispatch] = useReducer(reducer, initialState)\n","jsx","",[18,49,50],{"__ignoreMap":47},[51,52,55,59,63,67,70,73,76,79,83],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"szBVR","const",[51,60,62],{"class":61},"sVt8B"," [",[51,64,66],{"class":65},"sj4cs","state",[51,68,69],{"class":61},", ",[51,71,72],{"class":65},"dispatch",[51,74,75],{"class":61},"] ",[51,77,78],{"class":57},"=",[51,80,82],{"class":81},"sScJk"," useReducer",[51,84,85],{"class":61},"(reducer, initialState)\n",[87,88,89,99,105,110],"ul",{},[90,91,92,94,95,98],"li",{},[18,93,29],{}," — a pure function ",[18,96,97],{},"(state, action) => nextState",".",[90,100,101,104],{},[18,102,103],{},"initialState"," — the value for the first render.",[90,106,107,109],{},[18,108,66],{}," — the current state.",[90,111,112,114],{},[18,113,72],{}," — sends an action to the reducer; React calls the reducer with the current\nstate and the action, then re-renders with the returned state.",[15,116,117,118,120,121,124],{},"React guarantees that ",[18,119,72],{}," has a ",[27,122,123],{},"stable identity"," across renders — you can pass\nit through context or include it in dependency arrays without causing re-renders.",[10,126,128],{"id":127},"the-reducer-function","The reducer function",[15,130,131,132,135],{},"A reducer takes the current state and an action and returns the next state. It must be\n",[27,133,134],{},"pure",": no side effects, no mutation of the state argument, deterministic output.",[42,137,139],{"className":44,"code":138,"language":46,"meta":47,"style":47},"function cartReducer(state, action) {\n  switch (action.type) {\n    case 'add_item':\n      return { ...state, items: [...state.items, action.item] }\n    case 'remove_item':\n      return { ...state, items: state.items.filter(i => i.id !== action.id) }\n    case 'clear':\n      return { ...state, items: [] }\n    default:\n      return state \u002F\u002F always return current state for unknown actions\n  }\n}\n",[18,140,141,163,172,185,205,215,247,257,269,277,289,295],{"__ignoreMap":47},[51,142,143,146,149,152,155,157,160],{"class":53,"line":54},[51,144,145],{"class":57},"function",[51,147,148],{"class":81}," cartReducer",[51,150,151],{"class":61},"(",[51,153,66],{"class":154},"s4XuR",[51,156,69],{"class":61},[51,158,159],{"class":154},"action",[51,161,162],{"class":61},") {\n",[51,164,166,169],{"class":53,"line":165},2,[51,167,168],{"class":57},"  switch",[51,170,171],{"class":61}," (action.type) {\n",[51,173,175,178,182],{"class":53,"line":174},3,[51,176,177],{"class":57},"    case",[51,179,181],{"class":180},"sZZnC"," 'add_item'",[51,183,184],{"class":61},":\n",[51,186,188,191,194,197,200,202],{"class":53,"line":187},4,[51,189,190],{"class":57},"      return",[51,192,193],{"class":61}," { ",[51,195,196],{"class":57},"...",[51,198,199],{"class":61},"state, items: [",[51,201,196],{"class":57},[51,203,204],{"class":61},"state.items, action.item] }\n",[51,206,208,210,213],{"class":53,"line":207},5,[51,209,177],{"class":57},[51,211,212],{"class":180}," 'remove_item'",[51,214,184],{"class":61},[51,216,218,220,222,224,227,230,232,235,238,241,244],{"class":53,"line":217},6,[51,219,190],{"class":57},[51,221,193],{"class":61},[51,223,196],{"class":57},[51,225,226],{"class":61},"state, items: state.items.",[51,228,229],{"class":81},"filter",[51,231,151],{"class":61},[51,233,234],{"class":154},"i",[51,236,237],{"class":57}," =>",[51,239,240],{"class":61}," i.id ",[51,242,243],{"class":57},"!==",[51,245,246],{"class":61}," action.id) }\n",[51,248,250,252,255],{"class":53,"line":249},7,[51,251,177],{"class":57},[51,253,254],{"class":180}," 'clear'",[51,256,184],{"class":61},[51,258,260,262,264,266],{"class":53,"line":259},8,[51,261,190],{"class":57},[51,263,193],{"class":61},[51,265,196],{"class":57},[51,267,268],{"class":61},"state, items: [] }\n",[51,270,272,275],{"class":53,"line":271},9,[51,273,274],{"class":57},"    default",[51,276,184],{"class":61},[51,278,280,282,285],{"class":53,"line":279},10,[51,281,190],{"class":57},[51,283,284],{"class":61}," state ",[51,286,288],{"class":287},"sJ8bj","\u002F\u002F always return current state for unknown actions\n",[51,290,292],{"class":53,"line":291},11,[51,293,294],{"class":61},"  }\n",[51,296,298],{"class":53,"line":297},12,[51,299,300],{"class":61},"}\n",[15,302,303,304,307,308,311,312,314,315,318],{},"Two rules worth memorizing for interviews: (1) return a ",[27,305,306],{},"new"," object\u002Farray, never\nmutate the argument; (2) always handle ",[18,309,310],{},"default"," by returning ",[18,313,66],{}," — returning\n",[18,316,317],{},"undefined"," causes subtle render bugs.",[10,320,322],{"id":321},"action-conventions","Action conventions",[15,324,325,326,329,330,98],{},"Actions are plain objects. The ",[18,327,328],{},"type"," field identifies what happened; any extra data is\nusually called ",[18,331,332],{},"payload",[42,334,336],{"className":44,"code":335,"language":46,"meta":47,"style":47},"dispatch({ type: 'add_item', item: { id: 1, name: 'Widget', qty: 2 } })\ndispatch({ type: 'remove_item', id: 1 })\n",[18,337,338,369],{"__ignoreMap":47},[51,339,340,342,345,348,351,354,357,360,363,366],{"class":53,"line":54},[51,341,72],{"class":81},[51,343,344],{"class":61},"({ type: ",[51,346,347],{"class":180},"'add_item'",[51,349,350],{"class":61},", item: { id: ",[51,352,353],{"class":65},"1",[51,355,356],{"class":61},", name: ",[51,358,359],{"class":180},"'Widget'",[51,361,362],{"class":61},", qty: ",[51,364,365],{"class":65},"2",[51,367,368],{"class":61}," } })\n",[51,370,371,373,375,378,381,383],{"class":53,"line":165},[51,372,72],{"class":81},[51,374,344],{"class":61},[51,376,377],{"class":180},"'remove_item'",[51,379,380],{"class":61},", id: ",[51,382,353],{"class":65},[51,384,385],{"class":61}," })\n",[15,387,388,389,391],{},"Define type values as named constants or a TypeScript union to catch typos at compile\ntime — a misspelled string silently falls through to ",[18,390,310],{}," with no runtime error.",[42,393,397],{"className":394,"code":395,"language":396,"meta":47,"style":47},"language-ts shiki shiki-themes github-light github-dark","type CartAction =\n  | { type: 'add_item'; item: Item }\n  | { type: 'remove_item'; id: number }\n  | { type: 'clear' }\n","ts",[18,398,399,409,437,461],{"__ignoreMap":47},[51,400,401,403,406],{"class":53,"line":54},[51,402,328],{"class":57},[51,404,405],{"class":81}," CartAction",[51,407,408],{"class":57}," =\n",[51,410,411,414,416,418,421,423,426,429,431,434],{"class":53,"line":165},[51,412,413],{"class":57},"  |",[51,415,193],{"class":61},[51,417,328],{"class":154},[51,419,420],{"class":57},":",[51,422,181],{"class":180},[51,424,425],{"class":61},"; ",[51,427,428],{"class":154},"item",[51,430,420],{"class":57},[51,432,433],{"class":81}," Item",[51,435,436],{"class":61}," }\n",[51,438,439,441,443,445,447,449,451,454,456,459],{"class":53,"line":174},[51,440,413],{"class":57},[51,442,193],{"class":61},[51,444,328],{"class":154},[51,446,420],{"class":57},[51,448,212],{"class":180},[51,450,425],{"class":61},[51,452,453],{"class":154},"id",[51,455,420],{"class":57},[51,457,458],{"class":65}," number",[51,460,436],{"class":61},[51,462,463,465,467,469,471,473],{"class":53,"line":187},[51,464,413],{"class":57},[51,466,193],{"class":61},[51,468,328],{"class":154},[51,470,420],{"class":57},[51,472,254],{"class":180},[51,474,436],{"class":61},[10,476,478],{"id":477},"when-to-prefer-usereducer-over-usestate","When to prefer useReducer over useState",[15,480,481,482,420],{},"Three signals that you should reach for ",[18,483,24],{},[485,486,487,503,516],"ol",{},[90,488,489,492,493,69,496,69,499,502],{},[27,490,491],{},"Multiple sub-values that change together."," If you always call three setters at\nonce (",[18,494,495],{},"setLoading",[18,497,498],{},"setError",[18,500,501],{},"setData","), a single dispatch is cleaner.",[90,504,505,508,509,511,512,515],{},[27,506,507],{},"Complex update logic."," If your ",[18,510,20],{}," handlers contain ",[18,513,514],{},"if\u002Felse"," trees\nor switch statements, move that logic into the reducer where it's testable.",[90,517,518,521],{},[27,519,520],{},"You want to share update logic."," A reducer is a plain function — easy to import,\ncompose, and unit-test without rendering.",[42,523,525],{"className":44,"code":524,"language":46,"meta":47,"style":47},"\u002F\u002F before: three scattered setters\nfunction handleFetch() {\n  setLoading(true)\n  setError(null)\n  setData(null)\n}\n\n\u002F\u002F after: one dispatch, all transitions in one place\ndispatch({ type: 'fetch_start' })\n",[18,526,527,532,542,555,567,578,582,588,593],{"__ignoreMap":47},[51,528,529],{"class":53,"line":54},[51,530,531],{"class":287},"\u002F\u002F before: three scattered setters\n",[51,533,534,536,539],{"class":53,"line":165},[51,535,145],{"class":57},[51,537,538],{"class":81}," handleFetch",[51,540,541],{"class":61},"() {\n",[51,543,544,547,549,552],{"class":53,"line":174},[51,545,546],{"class":81},"  setLoading",[51,548,151],{"class":61},[51,550,551],{"class":65},"true",[51,553,554],{"class":61},")\n",[51,556,557,560,562,565],{"class":53,"line":187},[51,558,559],{"class":81},"  setError",[51,561,151],{"class":61},[51,563,564],{"class":65},"null",[51,566,554],{"class":61},[51,568,569,572,574,576],{"class":53,"line":207},[51,570,571],{"class":81},"  setData",[51,573,151],{"class":61},[51,575,564],{"class":65},[51,577,554],{"class":61},[51,579,580],{"class":53,"line":217},[51,581,300],{"class":61},[51,583,584],{"class":53,"line":249},[51,585,587],{"emptyLinePlaceholder":586},true,"\n",[51,589,590],{"class":53,"line":259},[51,591,592],{"class":287},"\u002F\u002F after: one dispatch, all transitions in one place\n",[51,594,595,597,599,602],{"class":53,"line":271},[51,596,72],{"class":81},[51,598,344],{"class":61},[51,600,601],{"class":180},"'fetch_start'",[51,603,385],{"class":61},[10,605,607],{"id":606},"the-initializer-function-third-argument","The initializer function (third argument)",[15,609,610,613,614,617],{},[18,611,612],{},"useReducer(reducer, arg, init)"," — the optional third argument is a function that\nReact calls as ",[18,615,616],{},"init(arg)"," on the first render to compute the initial state. This avoids\nrunning an expensive computation on every render (the second-argument expression is\nevaluated each time the component renders, even though it's only used once).",[42,619,621],{"className":44,"code":620,"language":46,"meta":47,"style":47},"function parseSettings(raw) {\n  return { theme: raw.theme ?? 'dark', lang: raw.lang ?? 'en' } \u002F\u002F expensive parse\n}\n\nconst [settings, dispatch] = useReducer(settingsReducer, rawData, parseSettings)\n\u002F\u002F parseSettings(rawData) only runs on mount\n",[18,622,623,637,665,669,673,695],{"__ignoreMap":47},[51,624,625,627,630,632,635],{"class":53,"line":54},[51,626,145],{"class":57},[51,628,629],{"class":81}," parseSettings",[51,631,151],{"class":61},[51,633,634],{"class":154},"raw",[51,636,162],{"class":61},[51,638,639,642,645,648,651,654,656,659,662],{"class":53,"line":165},[51,640,641],{"class":57},"  return",[51,643,644],{"class":61}," { theme: raw.theme ",[51,646,647],{"class":57},"??",[51,649,650],{"class":180}," 'dark'",[51,652,653],{"class":61},", lang: raw.lang ",[51,655,647],{"class":57},[51,657,658],{"class":180}," 'en'",[51,660,661],{"class":61}," } ",[51,663,664],{"class":287},"\u002F\u002F expensive parse\n",[51,666,667],{"class":53,"line":174},[51,668,300],{"class":61},[51,670,671],{"class":53,"line":187},[51,672,587],{"emptyLinePlaceholder":586},[51,674,675,677,679,682,684,686,688,690,692],{"class":53,"line":207},[51,676,58],{"class":57},[51,678,62],{"class":61},[51,680,681],{"class":65},"settings",[51,683,69],{"class":61},[51,685,72],{"class":65},[51,687,75],{"class":61},[51,689,78],{"class":57},[51,691,82],{"class":81},[51,693,694],{"class":61},"(settingsReducer, rawData, parseSettings)\n",[51,696,697],{"class":53,"line":217},[51,698,699],{"class":287},"\u002F\u002F parseSettings(rawData) only runs on mount\n",[15,701,702,703,706],{},"The initializer also enables a clean reset action — the reducer can call ",[18,704,705],{},"init(action.payload)"," to rebuild fresh state from a starting value.",[10,708,710],{"id":709},"sharing-dispatch-via-context","Sharing dispatch via context",[15,712,713,715,716,718,719,721,722,724],{},[18,714,72],{}," has a stable identity, so putting it in a context doesn't cause consumers to\nre-render on every state change. The pattern: a Provider holds the ",[18,717,24],{}," call and\nexposes both ",[18,720,66],{}," and ",[18,723,72],{}," through context.",[42,726,728],{"className":44,"code":727,"language":46,"meta":47,"style":47},"const StoreCtx = createContext(null)\n\nfunction StoreProvider({ children }) {\n  const [state, dispatch] = useReducer(reducer, initial)\n  return (\n    \u003CStoreCtx.Provider value={{ state, dispatch }}>\n      {children}\n    \u003C\u002FStoreCtx.Provider>\n  )\n}\n\nfunction CartButton() {\n  const { state, dispatch } = useContext(StoreCtx)\n  return (\n    \u003Cbutton onClick={() => dispatch({ type: 'clear' })}>\n      Clear ({state.items.length})\n    \u003C\u002Fbutton>\n  )\n}\n",[18,729,730,749,753,769,791,798,814,819,829,834,838,842,851,874,881,912,924,933,938],{"__ignoreMap":47},[51,731,732,734,737,740,743,745,747],{"class":53,"line":54},[51,733,58],{"class":57},[51,735,736],{"class":65}," StoreCtx",[51,738,739],{"class":57}," =",[51,741,742],{"class":81}," createContext",[51,744,151],{"class":61},[51,746,564],{"class":65},[51,748,554],{"class":61},[51,750,751],{"class":53,"line":165},[51,752,587],{"emptyLinePlaceholder":586},[51,754,755,757,760,763,766],{"class":53,"line":174},[51,756,145],{"class":57},[51,758,759],{"class":81}," StoreProvider",[51,761,762],{"class":61},"({ ",[51,764,765],{"class":154},"children",[51,767,768],{"class":61}," }) {\n",[51,770,771,774,776,778,780,782,784,786,788],{"class":53,"line":187},[51,772,773],{"class":57},"  const",[51,775,62],{"class":61},[51,777,66],{"class":65},[51,779,69],{"class":61},[51,781,72],{"class":65},[51,783,75],{"class":61},[51,785,78],{"class":57},[51,787,82],{"class":81},[51,789,790],{"class":61},"(reducer, initial)\n",[51,792,793,795],{"class":53,"line":207},[51,794,641],{"class":57},[51,796,797],{"class":61}," (\n",[51,799,800,803,806,809,811],{"class":53,"line":217},[51,801,802],{"class":61},"    \u003C",[51,804,805],{"class":65},"StoreCtx.Provider",[51,807,808],{"class":81}," value",[51,810,78],{"class":57},[51,812,813],{"class":61},"{{ state, dispatch }}>\n",[51,815,816],{"class":53,"line":249},[51,817,818],{"class":61},"      {children}\n",[51,820,821,824,826],{"class":53,"line":259},[51,822,823],{"class":61},"    \u003C\u002F",[51,825,805],{"class":65},[51,827,828],{"class":61},">\n",[51,830,831],{"class":53,"line":271},[51,832,833],{"class":61},"  )\n",[51,835,836],{"class":53,"line":279},[51,837,300],{"class":61},[51,839,840],{"class":53,"line":291},[51,841,587],{"emptyLinePlaceholder":586},[51,843,844,846,849],{"class":53,"line":297},[51,845,145],{"class":57},[51,847,848],{"class":81}," CartButton",[51,850,541],{"class":61},[51,852,854,856,858,860,862,864,866,868,871],{"class":53,"line":853},13,[51,855,773],{"class":57},[51,857,193],{"class":61},[51,859,66],{"class":65},[51,861,69],{"class":61},[51,863,72],{"class":65},[51,865,661],{"class":61},[51,867,78],{"class":57},[51,869,870],{"class":81}," useContext",[51,872,873],{"class":61},"(StoreCtx)\n",[51,875,877,879],{"class":53,"line":876},14,[51,878,641],{"class":57},[51,880,797],{"class":61},[51,882,884,886,890,893,895,898,901,904,906,909],{"class":53,"line":883},15,[51,885,802],{"class":61},[51,887,889],{"class":888},"s9eBZ","button",[51,891,892],{"class":81}," onClick",[51,894,78],{"class":57},[51,896,897],{"class":61},"{() ",[51,899,900],{"class":57},"=>",[51,902,903],{"class":81}," dispatch",[51,905,344],{"class":61},[51,907,908],{"class":180},"'clear'",[51,910,911],{"class":61}," })}>\n",[51,913,915,918,921],{"class":53,"line":914},16,[51,916,917],{"class":61},"      Clear ({state.items.",[51,919,920],{"class":65},"length",[51,922,923],{"class":61},"})\n",[51,925,927,929,931],{"class":53,"line":926},17,[51,928,823],{"class":61},[51,930,889],{"class":888},[51,932,828],{"class":61},[51,934,936],{"class":53,"line":935},18,[51,937,833],{"class":61},[51,939,941],{"class":53,"line":940},19,[51,942,300],{"class":61},[15,944,945,946,948,949,951],{},"For large apps, split the context: one for ",[18,947,66],{}," and one for ",[18,950,72],{},". Components\nthat only dispatch don't re-render when state changes.",[10,953,955],{"id":954},"testing-reducers","Testing reducers",[15,957,958],{},"Because a reducer is a pure function, you test it with zero React infrastructure —\njust call it and assert on the return value.",[42,960,964],{"className":961,"code":962,"language":963,"meta":47,"style":47},"language-js shiki shiki-themes github-light github-dark","import { cartReducer } from '.\u002FcartReducer'\n\ntest('adds an item', () => {\n  const state = { items: [] }\n  const next = cartReducer(state, { type: 'add_item', item: { id: 1, name: 'A' } })\n  expect(next.items).toHaveLength(1)\n  expect(next.items[0].id).toBe(1)\n})\n\ntest('unknown action returns same state', () => {\n  const state = { items: [] }\n  expect(cartReducer(state, { type: 'unknown' })).toBe(state) \u002F\u002F same reference\n})\n","js",[18,965,966,980,984,1002,1014,1041,1058,1080,1084,1088,1103,1113,1138],{"__ignoreMap":47},[51,967,968,971,974,977],{"class":53,"line":54},[51,969,970],{"class":57},"import",[51,972,973],{"class":61}," { cartReducer } ",[51,975,976],{"class":57},"from",[51,978,979],{"class":180}," '.\u002FcartReducer'\n",[51,981,982],{"class":53,"line":165},[51,983,587],{"emptyLinePlaceholder":586},[51,985,986,989,991,994,997,999],{"class":53,"line":174},[51,987,988],{"class":81},"test",[51,990,151],{"class":61},[51,992,993],{"class":180},"'adds an item'",[51,995,996],{"class":61},", () ",[51,998,900],{"class":57},[51,1000,1001],{"class":61}," {\n",[51,1003,1004,1006,1009,1011],{"class":53,"line":187},[51,1005,773],{"class":57},[51,1007,1008],{"class":65}," state",[51,1010,739],{"class":57},[51,1012,1013],{"class":61}," { items: [] }\n",[51,1015,1016,1018,1021,1023,1025,1028,1030,1032,1034,1036,1039],{"class":53,"line":207},[51,1017,773],{"class":57},[51,1019,1020],{"class":65}," next",[51,1022,739],{"class":57},[51,1024,148],{"class":81},[51,1026,1027],{"class":61},"(state, { type: ",[51,1029,347],{"class":180},[51,1031,350],{"class":61},[51,1033,353],{"class":65},[51,1035,356],{"class":61},[51,1037,1038],{"class":180},"'A'",[51,1040,368],{"class":61},[51,1042,1043,1046,1049,1052,1054,1056],{"class":53,"line":217},[51,1044,1045],{"class":81},"  expect",[51,1047,1048],{"class":61},"(next.items).",[51,1050,1051],{"class":81},"toHaveLength",[51,1053,151],{"class":61},[51,1055,353],{"class":65},[51,1057,554],{"class":61},[51,1059,1060,1062,1065,1068,1071,1074,1076,1078],{"class":53,"line":249},[51,1061,1045],{"class":81},[51,1063,1064],{"class":61},"(next.items[",[51,1066,1067],{"class":65},"0",[51,1069,1070],{"class":61},"].id).",[51,1072,1073],{"class":81},"toBe",[51,1075,151],{"class":61},[51,1077,353],{"class":65},[51,1079,554],{"class":61},[51,1081,1082],{"class":53,"line":259},[51,1083,923],{"class":61},[51,1085,1086],{"class":53,"line":271},[51,1087,587],{"emptyLinePlaceholder":586},[51,1089,1090,1092,1094,1097,1099,1101],{"class":53,"line":279},[51,1091,988],{"class":81},[51,1093,151],{"class":61},[51,1095,1096],{"class":180},"'unknown action returns same state'",[51,1098,996],{"class":61},[51,1100,900],{"class":57},[51,1102,1001],{"class":61},[51,1104,1105,1107,1109,1111],{"class":53,"line":291},[51,1106,773],{"class":57},[51,1108,1008],{"class":65},[51,1110,739],{"class":57},[51,1112,1013],{"class":61},[51,1114,1115,1117,1119,1122,1124,1127,1130,1132,1135],{"class":53,"line":297},[51,1116,1045],{"class":81},[51,1118,151],{"class":61},[51,1120,1121],{"class":81},"cartReducer",[51,1123,1027],{"class":61},[51,1125,1126],{"class":180},"'unknown'",[51,1128,1129],{"class":61}," })).",[51,1131,1073],{"class":81},[51,1133,1134],{"class":61},"(state) ",[51,1136,1137],{"class":287},"\u002F\u002F same reference\n",[51,1139,1140],{"class":53,"line":853},[51,1141,923],{"class":61},[15,1143,1144,1145,1147,1148,1150],{},"Testing the reducer independently is ",[18,1146,24],{},"'s key advantage over ",[18,1149,20],{}," —\nall business logic is in one pure function that exercises instantly.",[10,1152,1154],{"id":1153},"immer-for-simpler-updates","Immer for simpler updates",[15,1156,1157,1158,1161,1162,1165],{},"Deep immutable updates (spreading nested objects) get verbose. ",[27,1159,1160],{},"Immer","'s ",[18,1163,1164],{},"produce","\nwraps your reducer so you can write mutation-looking code on a draft, and it produces\na correct immutable result.",[42,1167,1169],{"className":44,"code":1168,"language":46,"meta":47,"style":47},"import produce from 'immer'\n\nconst cartReducer = produce((draft, action) => {\n  switch (action.type) {\n    case 'add_item':\n      draft.items.push(action.item)  \u002F\u002F mutation syntax, but produces a new state\n      break\n    case 'update_qty':\n      const item = draft.items.find(i => i.id === action.id)\n      if (item) item.qty = action.qty\n      break\n  }\n})\n",[18,1170,1171,1183,1187,1215,1221,1229,1243,1248,1257,1287,1300,1304,1308],{"__ignoreMap":47},[51,1172,1173,1175,1178,1180],{"class":53,"line":54},[51,1174,970],{"class":57},[51,1176,1177],{"class":61}," produce ",[51,1179,976],{"class":57},[51,1181,1182],{"class":180}," 'immer'\n",[51,1184,1185],{"class":53,"line":165},[51,1186,587],{"emptyLinePlaceholder":586},[51,1188,1189,1191,1193,1195,1198,1201,1204,1206,1208,1211,1213],{"class":53,"line":174},[51,1190,58],{"class":57},[51,1192,148],{"class":65},[51,1194,739],{"class":57},[51,1196,1197],{"class":81}," produce",[51,1199,1200],{"class":61},"((",[51,1202,1203],{"class":154},"draft",[51,1205,69],{"class":61},[51,1207,159],{"class":154},[51,1209,1210],{"class":61},") ",[51,1212,900],{"class":57},[51,1214,1001],{"class":61},[51,1216,1217,1219],{"class":53,"line":187},[51,1218,168],{"class":57},[51,1220,171],{"class":61},[51,1222,1223,1225,1227],{"class":53,"line":207},[51,1224,177],{"class":57},[51,1226,181],{"class":180},[51,1228,184],{"class":61},[51,1230,1231,1234,1237,1240],{"class":53,"line":217},[51,1232,1233],{"class":61},"      draft.items.",[51,1235,1236],{"class":81},"push",[51,1238,1239],{"class":61},"(action.item)  ",[51,1241,1242],{"class":287},"\u002F\u002F mutation syntax, but produces a new state\n",[51,1244,1245],{"class":53,"line":249},[51,1246,1247],{"class":57},"      break\n",[51,1249,1250,1252,1255],{"class":53,"line":259},[51,1251,177],{"class":57},[51,1253,1254],{"class":180}," 'update_qty'",[51,1256,184],{"class":61},[51,1258,1259,1262,1265,1267,1270,1273,1275,1277,1279,1281,1284],{"class":53,"line":271},[51,1260,1261],{"class":57},"      const",[51,1263,1264],{"class":65}," item",[51,1266,739],{"class":57},[51,1268,1269],{"class":61}," draft.items.",[51,1271,1272],{"class":81},"find",[51,1274,151],{"class":61},[51,1276,234],{"class":154},[51,1278,237],{"class":57},[51,1280,240],{"class":61},[51,1282,1283],{"class":57},"===",[51,1285,1286],{"class":61}," action.id)\n",[51,1288,1289,1292,1295,1297],{"class":53,"line":279},[51,1290,1291],{"class":57},"      if",[51,1293,1294],{"class":61}," (item) item.qty ",[51,1296,78],{"class":57},[51,1298,1299],{"class":61}," action.qty\n",[51,1301,1302],{"class":53,"line":291},[51,1303,1247],{"class":57},[51,1305,1306],{"class":53,"line":297},[51,1307,294],{"class":61},[51,1309,1310],{"class":53,"line":853},[51,1311,923],{"class":61},[15,1313,1314,1315,1317],{},"Redux Toolkit uses Immer internally for exactly this reason. If you're rolling your own\n",[18,1316,24],{}," + context store, Immer is worth adding when update logic becomes nested.",[10,1319,1321],{"id":1320},"usereducer-vs-redux","useReducer vs Redux",[1323,1324,1325,1341],"table",{},[1326,1327,1328],"thead",{},[1329,1330,1331,1335,1338],"tr",{},[1332,1333,1334],"th",{},"Feature",[1332,1336,1337],{},"useReducer + context",[1332,1339,1340],{},"Redux Toolkit",[1342,1343,1344,1356,1367,1377,1388],"tbody",{},[1329,1345,1346,1350,1353],{},[1347,1348,1349],"td",{},"Scope",[1347,1351,1352],{},"component tree",[1347,1354,1355],{},"global singleton",[1329,1357,1358,1361,1364],{},[1347,1359,1360],{},"DevTools",[1347,1362,1363],{},"none",[1347,1365,1366],{},"yes, time travel",[1329,1368,1369,1372,1374],{},[1347,1370,1371],{},"Middleware",[1347,1373,1363],{},[1347,1375,1376],{},"thunk, saga, RTK Query",[1329,1378,1379,1382,1385],{},[1347,1380,1381],{},"Boilerplate",[1347,1383,1384],{},"low",[1347,1386,1387],{},"low (RTK)",[1329,1389,1390,1393,1396],{},[1347,1391,1392],{},"Selectors",[1347,1394,1395],{},"manual",[1347,1397,1398,1401,1402],{},[18,1399,1400],{},"createSelector"," + ",[18,1403,1404],{},"useSelector",[15,1406,1407,1409],{},[18,1408,24],{}," + context is often enough for module-level or feature-scoped state. When\nyou need cross-cutting global state with selectors (so only interested components\nre-render), async middleware, or the Redux DevTools for debugging, Redux Toolkit earns\nits dependency.",[10,1411,1413],{"id":1412},"common-interview-questions-at-a-glance","Common interview questions at a glance",[87,1415,1416,1426,1432,1438,1444],{},[90,1417,1418,1421,1422,1425],{},[27,1419,1420],{},"What does dispatch do?"," Sends an action to the reducer; React calls\n",[18,1423,1424],{},"reducer(currentState, action)"," and re-renders with the returned state.",[90,1427,1428,1431],{},[27,1429,1430],{},"What must a reducer not do?"," Have side effects or mutate its state argument.",[90,1433,1434,1437],{},[27,1435,1436],{},"How is dispatch stable?"," React guarantees the same dispatch reference across\nrenders — safe in dependency arrays and context.",[90,1439,1440,1443],{},[27,1441,1442],{},"When would you use useReducer over useState?"," Multiple fields that change together,\ncomplex branchy update logic, or when you want unit-testable transitions.",[90,1445,1446,1449,1450,1453,1454,1456],{},[27,1447,1448],{},"How do you reset state?"," Handle a ",[18,1451,1452],{},"reset"," action type that returns ",[18,1455,103],{},",\nor call the initializer function with a starting value.",[1458,1459,1460],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":47,"searchDepth":165,"depth":165,"links":1462},[1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473],{"id":12,"depth":165,"text":13},{"id":39,"depth":165,"text":40},{"id":127,"depth":165,"text":128},{"id":321,"depth":165,"text":322},{"id":477,"depth":165,"text":478},{"id":606,"depth":165,"text":607},{"id":709,"depth":165,"text":710},{"id":954,"depth":165,"text":955},{"id":1153,"depth":165,"text":1154},{"id":1320,"depth":165,"text":1321},{"id":1412,"depth":165,"text":1413},"React useReducer interview guide — reducer functions, dispatch, action types, the initializer pattern, context integration, Immer, and when to prefer useReducer over useState.","medium","md","React","react",{},"\u002Fblog\u002Freact-usereducer-hook-guide","\u002Freact\u002Fhooks\u002Fusereducer",{"title":5,"description":1474},"blog\u002Freact-usereducer-hook-guide","Hooks","hooks","2026-06-23","qXUVs4rS5BpljY7MzM5JyXE_zyPDXMFTerIw-PQgIzE",1782244083400]