[{"data":1,"prerenderedAt":2142},["ShallowReactive",2],{"page-\u002Freact\u002F10-usereducer-and-complex-state":3},{"id":4,"title":5,"body":6,"description":2135,"extension":2136,"meta":2137,"navigation":57,"path":2138,"seo":2139,"stem":2140,"__hash__":2141},"content\u002Freact\u002F10-usereducer-and-complex-state.md","10 — useReducer & Complex State",{"type":7,"value":8,"toc":2124},"minimark",[9,19,24,136,140,620,624,1058,1062,1479,1483,1651,1655,1754,1758,1843,1847,1851,1940,2032,2036,2120],[10,11,13,14,18],"h1",{"id":12},"_10-usereducer-complex-state","10 — ",[15,16,17],"code",{},"useReducer"," & Complex State",[20,21,23],"h2",{"id":22},"usestate-vs-usereducer-the-decision","useState vs useReducer: The Decision",[25,26,29],"code-wrapper",{"filename":27,"language":28},"usestate_vs_usereducer.js","javascript",[30,31,35],"pre",{"className":32,"code":33,"language":28,"meta":34,"style":34},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F useState: independent values, simple updates, 1-3 state fields\n\u002F\u002F useReducer: interdependent state, complex transitions, many fields, state machines\n\n\u002F\u002F USE useState WHEN:\n\u002F\u002F - State is a single primitive or simple object\n\u002F\u002F - Updates are independent (setA doesn't affect B)\n\u002F\u002F - Few state fields (1-3)\n\u002F\u002F - Update logic is a one-liner\n\n\u002F\u002F USE useReducer WHEN:\n\u002F\u002F - Multiple state fields update together (interdependent)\n\u002F\u002F - Next state depends on complex conditions\n\u002F\u002F - You need a clear audit trail of state transitions (actions)\n\u002F\u002F - State has a finite set of well-defined \"modes\" or \"phases\"\n\u002F\u002F - Update logic is more than 2-3 lines\n\u002F\u002F - Testing the state logic separately matters (reducers are pure functions)\n","",[15,36,37,46,52,59,65,71,77,83,89,94,100,106,112,118,124,130],{"__ignoreMap":34},[38,39,42],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"sdCPZ","\u002F\u002F useState: independent values, simple updates, 1-3 state fields\n",[38,47,49],{"class":40,"line":48},2,[38,50,51],{"class":44},"\u002F\u002F useReducer: interdependent state, complex transitions, many fields, state machines\n",[38,53,55],{"class":40,"line":54},3,[38,56,58],{"emptyLinePlaceholder":57},true,"\n",[38,60,62],{"class":40,"line":61},4,[38,63,64],{"class":44},"\u002F\u002F USE useState WHEN:\n",[38,66,68],{"class":40,"line":67},5,[38,69,70],{"class":44},"\u002F\u002F - State is a single primitive or simple object\n",[38,72,74],{"class":40,"line":73},6,[38,75,76],{"class":44},"\u002F\u002F - Updates are independent (setA doesn't affect B)\n",[38,78,80],{"class":40,"line":79},7,[38,81,82],{"class":44},"\u002F\u002F - Few state fields (1-3)\n",[38,84,86],{"class":40,"line":85},8,[38,87,88],{"class":44},"\u002F\u002F - Update logic is a one-liner\n",[38,90,92],{"class":40,"line":91},9,[38,93,58],{"emptyLinePlaceholder":57},[38,95,97],{"class":40,"line":96},10,[38,98,99],{"class":44},"\u002F\u002F USE useReducer WHEN:\n",[38,101,103],{"class":40,"line":102},11,[38,104,105],{"class":44},"\u002F\u002F - Multiple state fields update together (interdependent)\n",[38,107,109],{"class":40,"line":108},12,[38,110,111],{"class":44},"\u002F\u002F - Next state depends on complex conditions\n",[38,113,115],{"class":40,"line":114},13,[38,116,117],{"class":44},"\u002F\u002F - You need a clear audit trail of state transitions (actions)\n",[38,119,121],{"class":40,"line":120},14,[38,122,123],{"class":44},"\u002F\u002F - State has a finite set of well-defined \"modes\" or \"phases\"\n",[38,125,127],{"class":40,"line":126},15,[38,128,129],{"class":44},"\u002F\u002F - Update logic is more than 2-3 lines\n",[38,131,133],{"class":40,"line":132},16,[38,134,135],{"class":44},"\u002F\u002F - Testing the state logic separately matters (reducers are pure functions)\n",[20,137,139],{"id":138},"usereducer-anatomy","useReducer Anatomy",[25,141,143],{"filename":142,"language":28},"usereducer_anatomy.js",[30,144,146],{"className":32,"code":145,"language":28,"meta":34,"style":34},"import { useReducer } from 'react'\n\n\u002F\u002F useReducer(reducer, initialState) → [state, dispatch]\n\u002F\u002F reducer: (state, action) → newState  (PURE function, no side effects)\n\u002F\u002F action: { type: string, ...payload }\n\nconst initialState = { count: 0, step: 1 }\n\nfunction counterReducer(state, action) {\n  switch (action.type) {\n    case 'increment':\n      return { ...state, count: state.count + state.step }\n    case 'decrement':\n      return { ...state, count: state.count - state.step }\n    case 'setStep':\n      return { ...state, step: action.step }\n    case 'reset':\n      return initialState\n    default:\n      \u002F\u002F CRITICAL: always return state in default — throwing is also acceptable.\n      \u002F\u002F Returning state (not throwing) means unknown actions are silently ignored.\n      \u002F\u002F Throwing catches bugs in development: assertUnreachable(action.type)\n      return state\n  }\n}\n\nfunction Counter() {\n  const [state, dispatch] = useReducer(counterReducer, initialState)\n  return (\n    \u003Cdiv>\n      \u003Cspan>{state.count}\u003C\u002Fspan>\n      \u003Cbutton onClick={() => dispatch({ type: 'increment' })}>+\u003C\u002Fbutton>\n      \u003Cbutton onClick={() => dispatch({ type: 'decrement' })}>−\u003C\u002Fbutton>\n      \u003Cbutton onClick={() => dispatch({ type: 'setStep', step: 5 })}>Step 5\u003C\u002Fbutton>\n      \u003Cbutton onClick={() => dispatch({ type: 'reset' })}>Reset\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )\n}\n",[15,147,148,165,169,174,179,184,188,215,219,244,252,263,283,292,307,316,327,337,345,353,359,365,371,379,385,391,396,407,435,444,457,472,507,536,570,599,609,615],{"__ignoreMap":34},[38,149,150,154,158,161],{"class":40,"line":41},[38,151,153],{"class":152},"svdQ7","import",[38,155,157],{"class":156},"ssxIu"," { useReducer } ",[38,159,160],{"class":152},"from",[38,162,164],{"class":163},"sJ6F3"," 'react'\n",[38,166,167],{"class":40,"line":48},[38,168,58],{"emptyLinePlaceholder":57},[38,170,171],{"class":40,"line":54},[38,172,173],{"class":44},"\u002F\u002F useReducer(reducer, initialState) → [state, dispatch]\n",[38,175,176],{"class":40,"line":61},[38,177,178],{"class":44},"\u002F\u002F reducer: (state, action) → newState  (PURE function, no side effects)\n",[38,180,181],{"class":40,"line":67},[38,182,183],{"class":44},"\u002F\u002F action: { type: string, ...payload }\n",[38,185,186],{"class":40,"line":73},[38,187,58],{"emptyLinePlaceholder":57},[38,189,190,193,197,200,203,206,209,212],{"class":40,"line":79},[38,191,192],{"class":152},"const",[38,194,196],{"class":195},"snvgF"," initialState",[38,198,199],{"class":152}," =",[38,201,202],{"class":156}," { count: ",[38,204,205],{"class":195},"0",[38,207,208],{"class":156},", step: ",[38,210,211],{"class":195},"1",[38,213,214],{"class":156}," }\n",[38,216,217],{"class":40,"line":85},[38,218,58],{"emptyLinePlaceholder":57},[38,220,221,224,228,231,235,238,241],{"class":40,"line":91},[38,222,223],{"class":152},"function",[38,225,227],{"class":226},"sIsaT"," counterReducer",[38,229,230],{"class":156},"(",[38,232,234],{"class":233},"sCrzJ","state",[38,236,237],{"class":156},", ",[38,239,240],{"class":233},"action",[38,242,243],{"class":156},") {\n",[38,245,246,249],{"class":40,"line":96},[38,247,248],{"class":152},"  switch",[38,250,251],{"class":156}," (action.type) {\n",[38,253,254,257,260],{"class":40,"line":102},[38,255,256],{"class":152},"    case",[38,258,259],{"class":163}," 'increment'",[38,261,262],{"class":156},":\n",[38,264,265,268,271,274,277,280],{"class":40,"line":108},[38,266,267],{"class":152},"      return",[38,269,270],{"class":156}," { ",[38,272,273],{"class":152},"...",[38,275,276],{"class":156},"state, count: state.count ",[38,278,279],{"class":152},"+",[38,281,282],{"class":156}," state.step }\n",[38,284,285,287,290],{"class":40,"line":114},[38,286,256],{"class":152},[38,288,289],{"class":163}," 'decrement'",[38,291,262],{"class":156},[38,293,294,296,298,300,302,305],{"class":40,"line":120},[38,295,267],{"class":152},[38,297,270],{"class":156},[38,299,273],{"class":152},[38,301,276],{"class":156},[38,303,304],{"class":152},"-",[38,306,282],{"class":156},[38,308,309,311,314],{"class":40,"line":126},[38,310,256],{"class":152},[38,312,313],{"class":163}," 'setStep'",[38,315,262],{"class":156},[38,317,318,320,322,324],{"class":40,"line":132},[38,319,267],{"class":152},[38,321,270],{"class":156},[38,323,273],{"class":152},[38,325,326],{"class":156},"state, step: action.step }\n",[38,328,330,332,335],{"class":40,"line":329},17,[38,331,256],{"class":152},[38,333,334],{"class":163}," 'reset'",[38,336,262],{"class":156},[38,338,340,342],{"class":40,"line":339},18,[38,341,267],{"class":152},[38,343,344],{"class":156}," initialState\n",[38,346,348,351],{"class":40,"line":347},19,[38,349,350],{"class":152},"    default",[38,352,262],{"class":156},[38,354,356],{"class":40,"line":355},20,[38,357,358],{"class":44},"      \u002F\u002F CRITICAL: always return state in default — throwing is also acceptable.\n",[38,360,362],{"class":40,"line":361},21,[38,363,364],{"class":44},"      \u002F\u002F Returning state (not throwing) means unknown actions are silently ignored.\n",[38,366,368],{"class":40,"line":367},22,[38,369,370],{"class":44},"      \u002F\u002F Throwing catches bugs in development: assertUnreachable(action.type)\n",[38,372,374,376],{"class":40,"line":373},23,[38,375,267],{"class":152},[38,377,378],{"class":156}," state\n",[38,380,382],{"class":40,"line":381},24,[38,383,384],{"class":156},"  }\n",[38,386,388],{"class":40,"line":387},25,[38,389,390],{"class":156},"}\n",[38,392,394],{"class":40,"line":393},26,[38,395,58],{"emptyLinePlaceholder":57},[38,397,399,401,404],{"class":40,"line":398},27,[38,400,223],{"class":152},[38,402,403],{"class":226}," Counter",[38,405,406],{"class":156},"() {\n",[38,408,410,413,416,418,420,423,426,429,432],{"class":40,"line":409},28,[38,411,412],{"class":152},"  const",[38,414,415],{"class":156}," [",[38,417,234],{"class":195},[38,419,237],{"class":156},[38,421,422],{"class":195},"dispatch",[38,424,425],{"class":156},"] ",[38,427,428],{"class":152},"=",[38,430,431],{"class":226}," useReducer",[38,433,434],{"class":156},"(counterReducer, initialState)\n",[38,436,438,441],{"class":40,"line":437},29,[38,439,440],{"class":152},"  return",[38,442,443],{"class":156}," (\n",[38,445,447,450,454],{"class":40,"line":446},30,[38,448,449],{"class":156},"    \u003C",[38,451,453],{"class":452},"sk71V","div",[38,455,456],{"class":156},">\n",[38,458,460,463,465,468,470],{"class":40,"line":459},31,[38,461,462],{"class":156},"      \u003C",[38,464,38],{"class":452},[38,466,467],{"class":156},">{state.count}\u003C\u002F",[38,469,38],{"class":452},[38,471,456],{"class":156},[38,473,475,477,480,483,485,488,491,494,497,500,503,505],{"class":40,"line":474},32,[38,476,462],{"class":156},[38,478,479],{"class":452},"button",[38,481,482],{"class":226}," onClick",[38,484,428],{"class":152},[38,486,487],{"class":156},"{() ",[38,489,490],{"class":152},"=>",[38,492,493],{"class":226}," dispatch",[38,495,496],{"class":156},"({ type: ",[38,498,499],{"class":163},"'increment'",[38,501,502],{"class":156}," })}>+\u003C\u002F",[38,504,479],{"class":452},[38,506,456],{"class":156},[38,508,510,512,514,516,518,520,522,524,526,529,532,534],{"class":40,"line":509},33,[38,511,462],{"class":156},[38,513,479],{"class":452},[38,515,482],{"class":226},[38,517,428],{"class":152},[38,519,487],{"class":156},[38,521,490],{"class":152},[38,523,493],{"class":226},[38,525,496],{"class":156},[38,527,528],{"class":163},"'decrement'",[38,530,531],{"class":156}," })}>−\u003C\u002F",[38,533,479],{"class":452},[38,535,456],{"class":156},[38,537,539,541,543,545,547,549,551,553,555,558,560,563,566,568],{"class":40,"line":538},34,[38,540,462],{"class":156},[38,542,479],{"class":452},[38,544,482],{"class":226},[38,546,428],{"class":152},[38,548,487],{"class":156},[38,550,490],{"class":152},[38,552,493],{"class":226},[38,554,496],{"class":156},[38,556,557],{"class":163},"'setStep'",[38,559,208],{"class":156},[38,561,562],{"class":195},"5",[38,564,565],{"class":156}," })}>Step 5\u003C\u002F",[38,567,479],{"class":452},[38,569,456],{"class":156},[38,571,573,575,577,579,581,583,585,587,589,592,595,597],{"class":40,"line":572},35,[38,574,462],{"class":156},[38,576,479],{"class":452},[38,578,482],{"class":226},[38,580,428],{"class":152},[38,582,487],{"class":156},[38,584,490],{"class":152},[38,586,493],{"class":226},[38,588,496],{"class":156},[38,590,591],{"class":163},"'reset'",[38,593,594],{"class":156}," })}>Reset\u003C\u002F",[38,596,479],{"class":452},[38,598,456],{"class":156},[38,600,602,605,607],{"class":40,"line":601},36,[38,603,604],{"class":156},"    \u003C\u002F",[38,606,453],{"class":452},[38,608,456],{"class":156},[38,610,612],{"class":40,"line":611},37,[38,613,614],{"class":156},"  )\n",[38,616,618],{"class":40,"line":617},38,[38,619,390],{"class":156},[20,621,623],{"id":622},"production-pattern-multi-field-form","Production Pattern: Multi-Field Form",[25,625,627],{"filename":626,"language":28},"form_reducer.js",[30,628,630],{"className":32,"code":629,"language":28,"meta":34,"style":34},"const initialFormState = {\n  values: { name: '', email: '', password: '' },\n  errors: {},\n  touched: { name: false, email: false, password: false },\n  isSubmitting: false,\n}\n\nfunction formReducer(state, action) {\n  switch (action.type) {\n    case 'field_change':\n      \u002F\u002F Update a single field value, clear its error, keep touched as-is\n      return {\n        ...state,\n        values: { ...state.values, [action.field]: action.value },\n        errors: { ...state.errors, [action.field]: undefined },\n      }\n    case 'field_blur':\n      \u002F\u002F Mark field as touched and validate it\n      return {\n        ...state,\n        touched: { ...state.touched, [action.field]: true },\n        errors: {\n          ...state.errors,\n          [action.field]: validateField(action.field, state.values[action.field]),\n        },\n      }\n    case 'start_submit':\n      \u002F\u002F Validate all fields, set submitting only if no errors\n      const allErrors = validateAll(state.values)\n      return {\n        ...state,\n        touched: { name: true, email: true, password: true },\n        errors: allErrors,\n        isSubmitting: Object.keys(allErrors).length === 0,\n      }\n    case 'submit_success':\n      return { ...initialFormState }  \u002F\u002F reset everything\n    case 'submit_error':\n      return { ...state, isSubmitting: false, errors: { ...state.errors, submit: action.error } }\n    default:\n      return state\n  }\n}\n\n\u002F\u002F Usage: const [formState, dispatch] = useReducer(formReducer, initialFormState)\n\u002F\u002F dispatch({ type: 'field_change', field: 'email', value: 'user@test.com' })\n\u002F\u002F dispatch({ type: 'field_blur', field: 'email' })\n\u002F\u002F dispatch({ type: 'start_submit' })\n",[15,631,632,644,665,670,688,698,702,706,723,729,738,743,749,757,767,782,787,796,801,807,813,828,833,841,852,857,861,870,875,891,897,903,920,925,947,951,960,974,983,1005,1012,1019,1024,1029,1034,1040,1046,1052],{"__ignoreMap":34},[38,633,634,636,639,641],{"class":40,"line":41},[38,635,192],{"class":152},[38,637,638],{"class":195}," initialFormState",[38,640,199],{"class":152},[38,642,643],{"class":156}," {\n",[38,645,646,649,652,655,657,660,662],{"class":40,"line":48},[38,647,648],{"class":156},"  values: { name: ",[38,650,651],{"class":163},"''",[38,653,654],{"class":156},", email: ",[38,656,651],{"class":163},[38,658,659],{"class":156},", password: ",[38,661,651],{"class":163},[38,663,664],{"class":156}," },\n",[38,666,667],{"class":40,"line":54},[38,668,669],{"class":156},"  errors: {},\n",[38,671,672,675,678,680,682,684,686],{"class":40,"line":61},[38,673,674],{"class":156},"  touched: { name: ",[38,676,677],{"class":195},"false",[38,679,654],{"class":156},[38,681,677],{"class":195},[38,683,659],{"class":156},[38,685,677],{"class":195},[38,687,664],{"class":156},[38,689,690,693,695],{"class":40,"line":67},[38,691,692],{"class":156},"  isSubmitting: ",[38,694,677],{"class":195},[38,696,697],{"class":156},",\n",[38,699,700],{"class":40,"line":73},[38,701,390],{"class":156},[38,703,704],{"class":40,"line":79},[38,705,58],{"emptyLinePlaceholder":57},[38,707,708,710,713,715,717,719,721],{"class":40,"line":85},[38,709,223],{"class":152},[38,711,712],{"class":226}," formReducer",[38,714,230],{"class":156},[38,716,234],{"class":233},[38,718,237],{"class":156},[38,720,240],{"class":233},[38,722,243],{"class":156},[38,724,725,727],{"class":40,"line":91},[38,726,248],{"class":152},[38,728,251],{"class":156},[38,730,731,733,736],{"class":40,"line":96},[38,732,256],{"class":152},[38,734,735],{"class":163}," 'field_change'",[38,737,262],{"class":156},[38,739,740],{"class":40,"line":102},[38,741,742],{"class":44},"      \u002F\u002F Update a single field value, clear its error, keep touched as-is\n",[38,744,745,747],{"class":40,"line":108},[38,746,267],{"class":152},[38,748,643],{"class":156},[38,750,751,754],{"class":40,"line":114},[38,752,753],{"class":152},"        ...",[38,755,756],{"class":156},"state,\n",[38,758,759,762,764],{"class":40,"line":120},[38,760,761],{"class":156},"        values: { ",[38,763,273],{"class":152},[38,765,766],{"class":156},"state.values, [action.field]: action.value },\n",[38,768,769,772,774,777,780],{"class":40,"line":126},[38,770,771],{"class":156},"        errors: { ",[38,773,273],{"class":152},[38,775,776],{"class":156},"state.errors, [action.field]: ",[38,778,779],{"class":195},"undefined",[38,781,664],{"class":156},[38,783,784],{"class":40,"line":132},[38,785,786],{"class":156},"      }\n",[38,788,789,791,794],{"class":40,"line":329},[38,790,256],{"class":152},[38,792,793],{"class":163}," 'field_blur'",[38,795,262],{"class":156},[38,797,798],{"class":40,"line":339},[38,799,800],{"class":44},"      \u002F\u002F Mark field as touched and validate it\n",[38,802,803,805],{"class":40,"line":347},[38,804,267],{"class":152},[38,806,643],{"class":156},[38,808,809,811],{"class":40,"line":355},[38,810,753],{"class":152},[38,812,756],{"class":156},[38,814,815,818,820,823,826],{"class":40,"line":361},[38,816,817],{"class":156},"        touched: { ",[38,819,273],{"class":152},[38,821,822],{"class":156},"state.touched, [action.field]: ",[38,824,825],{"class":195},"true",[38,827,664],{"class":156},[38,829,830],{"class":40,"line":367},[38,831,832],{"class":156},"        errors: {\n",[38,834,835,838],{"class":40,"line":373},[38,836,837],{"class":152},"          ...",[38,839,840],{"class":156},"state.errors,\n",[38,842,843,846,849],{"class":40,"line":381},[38,844,845],{"class":156},"          [action.field]: ",[38,847,848],{"class":226},"validateField",[38,850,851],{"class":156},"(action.field, state.values[action.field]),\n",[38,853,854],{"class":40,"line":387},[38,855,856],{"class":156},"        },\n",[38,858,859],{"class":40,"line":393},[38,860,786],{"class":156},[38,862,863,865,868],{"class":40,"line":398},[38,864,256],{"class":152},[38,866,867],{"class":163}," 'start_submit'",[38,869,262],{"class":156},[38,871,872],{"class":40,"line":409},[38,873,874],{"class":44},"      \u002F\u002F Validate all fields, set submitting only if no errors\n",[38,876,877,880,883,885,888],{"class":40,"line":437},[38,878,879],{"class":152},"      const",[38,881,882],{"class":195}," allErrors",[38,884,199],{"class":152},[38,886,887],{"class":226}," validateAll",[38,889,890],{"class":156},"(state.values)\n",[38,892,893,895],{"class":40,"line":446},[38,894,267],{"class":152},[38,896,643],{"class":156},[38,898,899,901],{"class":40,"line":459},[38,900,753],{"class":152},[38,902,756],{"class":156},[38,904,905,908,910,912,914,916,918],{"class":40,"line":474},[38,906,907],{"class":156},"        touched: { name: ",[38,909,825],{"class":195},[38,911,654],{"class":156},[38,913,825],{"class":195},[38,915,659],{"class":156},[38,917,825],{"class":195},[38,919,664],{"class":156},[38,921,922],{"class":40,"line":509},[38,923,924],{"class":156},"        errors: allErrors,\n",[38,926,927,930,933,936,939,942,945],{"class":40,"line":538},[38,928,929],{"class":156},"        isSubmitting: Object.",[38,931,932],{"class":226},"keys",[38,934,935],{"class":156},"(allErrors).",[38,937,938],{"class":195},"length",[38,940,941],{"class":152}," ===",[38,943,944],{"class":195}," 0",[38,946,697],{"class":156},[38,948,949],{"class":40,"line":572},[38,950,786],{"class":156},[38,952,953,955,958],{"class":40,"line":601},[38,954,256],{"class":152},[38,956,957],{"class":163}," 'submit_success'",[38,959,262],{"class":156},[38,961,962,964,966,968,971],{"class":40,"line":611},[38,963,267],{"class":152},[38,965,270],{"class":156},[38,967,273],{"class":152},[38,969,970],{"class":156},"initialFormState }  ",[38,972,973],{"class":44},"\u002F\u002F reset everything\n",[38,975,976,978,981],{"class":40,"line":617},[38,977,256],{"class":152},[38,979,980],{"class":163}," 'submit_error'",[38,982,262],{"class":156},[38,984,986,988,990,992,995,997,1000,1002],{"class":40,"line":985},39,[38,987,267],{"class":152},[38,989,270],{"class":156},[38,991,273],{"class":152},[38,993,994],{"class":156},"state, isSubmitting: ",[38,996,677],{"class":195},[38,998,999],{"class":156},", errors: { ",[38,1001,273],{"class":152},[38,1003,1004],{"class":156},"state.errors, submit: action.error } }\n",[38,1006,1008,1010],{"class":40,"line":1007},40,[38,1009,350],{"class":152},[38,1011,262],{"class":156},[38,1013,1015,1017],{"class":40,"line":1014},41,[38,1016,267],{"class":152},[38,1018,378],{"class":156},[38,1020,1022],{"class":40,"line":1021},42,[38,1023,384],{"class":156},[38,1025,1027],{"class":40,"line":1026},43,[38,1028,390],{"class":156},[38,1030,1032],{"class":40,"line":1031},44,[38,1033,58],{"emptyLinePlaceholder":57},[38,1035,1037],{"class":40,"line":1036},45,[38,1038,1039],{"class":44},"\u002F\u002F Usage: const [formState, dispatch] = useReducer(formReducer, initialFormState)\n",[38,1041,1043],{"class":40,"line":1042},46,[38,1044,1045],{"class":44},"\u002F\u002F dispatch({ type: 'field_change', field: 'email', value: 'user@test.com' })\n",[38,1047,1049],{"class":40,"line":1048},47,[38,1050,1051],{"class":44},"\u002F\u002F dispatch({ type: 'field_blur', field: 'email' })\n",[38,1053,1055],{"class":40,"line":1054},48,[38,1056,1057],{"class":44},"\u002F\u002F dispatch({ type: 'start_submit' })\n",[20,1059,1061],{"id":1060},"state-machine-async-data-fetching","State Machine: Async Data Fetching",[25,1063,1065],{"filename":1064,"language":28},"async_state_machine.js",[30,1066,1068],{"className":32,"code":1067,"language":28,"meta":34,"style":34},"\u002F\u002F A finite state machine for async operations: idle → loading → success\u002Ferror\n\nconst initialState = { status: 'idle', data: null, error: null }\n\nfunction asyncReducer(state, action) {\n  switch (action.type) {\n    case 'fetch_start':\n      return { status: 'loading', data: null, error: null }\n    case 'fetch_success':\n      return { status: 'success', data: action.data, error: null }\n    case 'fetch_error':\n      return { status: 'error', data: null, error: action.error }\n    case 'reset':\n      return initialState\n    default:\n      return state\n  }\n}\n\nfunction useAsyncFetch(fetchFn, deps = []) {\n  const [state, dispatch] = useReducer(asyncReducer, initialState)\n\n  useEffect(() => {\n    let cancelled = false\n    dispatch({ type: 'fetch_start' })\n    fetchFn()\n      .then(data => { if (!cancelled) dispatch({ type: 'fetch_success', data }) })\n      .catch(error => { if (!cancelled) dispatch({ type: 'fetch_error', error: error.message }) })\n    return () => { cancelled = true }\n  }, deps)\n\n  return state\n}\n\n\u002F\u002F The state machine makes IMPOSSIBLE states unreachable:\n\u002F\u002F You can't be in \"loading\" and \"success\" at the same time.\n\u002F\u002F With separate useStates (setStatus, setData, setError), you CAN accidentally\n\u002F\u002F set inconsistent combinations: setStatus('loading') + setData(oldData).\n",[15,1069,1070,1075,1079,1106,1110,1127,1133,1142,1161,1170,1186,1195,1211,1219,1225,1231,1237,1241,1245,1249,1271,1292,1296,1308,1321,1334,1342,1382,1416,1436,1441,1445,1451,1455,1459,1464,1469,1474],{"__ignoreMap":34},[38,1071,1072],{"class":40,"line":41},[38,1073,1074],{"class":44},"\u002F\u002F A finite state machine for async operations: idle → loading → success\u002Ferror\n",[38,1076,1077],{"class":40,"line":48},[38,1078,58],{"emptyLinePlaceholder":57},[38,1080,1081,1083,1085,1087,1090,1093,1096,1099,1102,1104],{"class":40,"line":54},[38,1082,192],{"class":152},[38,1084,196],{"class":195},[38,1086,199],{"class":152},[38,1088,1089],{"class":156}," { status: ",[38,1091,1092],{"class":163},"'idle'",[38,1094,1095],{"class":156},", data: ",[38,1097,1098],{"class":195},"null",[38,1100,1101],{"class":156},", error: ",[38,1103,1098],{"class":195},[38,1105,214],{"class":156},[38,1107,1108],{"class":40,"line":61},[38,1109,58],{"emptyLinePlaceholder":57},[38,1111,1112,1114,1117,1119,1121,1123,1125],{"class":40,"line":67},[38,1113,223],{"class":152},[38,1115,1116],{"class":226}," asyncReducer",[38,1118,230],{"class":156},[38,1120,234],{"class":233},[38,1122,237],{"class":156},[38,1124,240],{"class":233},[38,1126,243],{"class":156},[38,1128,1129,1131],{"class":40,"line":73},[38,1130,248],{"class":152},[38,1132,251],{"class":156},[38,1134,1135,1137,1140],{"class":40,"line":79},[38,1136,256],{"class":152},[38,1138,1139],{"class":163}," 'fetch_start'",[38,1141,262],{"class":156},[38,1143,1144,1146,1148,1151,1153,1155,1157,1159],{"class":40,"line":85},[38,1145,267],{"class":152},[38,1147,1089],{"class":156},[38,1149,1150],{"class":163},"'loading'",[38,1152,1095],{"class":156},[38,1154,1098],{"class":195},[38,1156,1101],{"class":156},[38,1158,1098],{"class":195},[38,1160,214],{"class":156},[38,1162,1163,1165,1168],{"class":40,"line":91},[38,1164,256],{"class":152},[38,1166,1167],{"class":163}," 'fetch_success'",[38,1169,262],{"class":156},[38,1171,1172,1174,1176,1179,1182,1184],{"class":40,"line":96},[38,1173,267],{"class":152},[38,1175,1089],{"class":156},[38,1177,1178],{"class":163},"'success'",[38,1180,1181],{"class":156},", data: action.data, error: ",[38,1183,1098],{"class":195},[38,1185,214],{"class":156},[38,1187,1188,1190,1193],{"class":40,"line":102},[38,1189,256],{"class":152},[38,1191,1192],{"class":163}," 'fetch_error'",[38,1194,262],{"class":156},[38,1196,1197,1199,1201,1204,1206,1208],{"class":40,"line":108},[38,1198,267],{"class":152},[38,1200,1089],{"class":156},[38,1202,1203],{"class":163},"'error'",[38,1205,1095],{"class":156},[38,1207,1098],{"class":195},[38,1209,1210],{"class":156},", error: action.error }\n",[38,1212,1213,1215,1217],{"class":40,"line":114},[38,1214,256],{"class":152},[38,1216,334],{"class":163},[38,1218,262],{"class":156},[38,1220,1221,1223],{"class":40,"line":120},[38,1222,267],{"class":152},[38,1224,344],{"class":156},[38,1226,1227,1229],{"class":40,"line":126},[38,1228,350],{"class":152},[38,1230,262],{"class":156},[38,1232,1233,1235],{"class":40,"line":132},[38,1234,267],{"class":152},[38,1236,378],{"class":156},[38,1238,1239],{"class":40,"line":329},[38,1240,384],{"class":156},[38,1242,1243],{"class":40,"line":339},[38,1244,390],{"class":156},[38,1246,1247],{"class":40,"line":347},[38,1248,58],{"emptyLinePlaceholder":57},[38,1250,1251,1253,1256,1258,1261,1263,1266,1268],{"class":40,"line":355},[38,1252,223],{"class":152},[38,1254,1255],{"class":226}," useAsyncFetch",[38,1257,230],{"class":156},[38,1259,1260],{"class":233},"fetchFn",[38,1262,237],{"class":156},[38,1264,1265],{"class":233},"deps",[38,1267,199],{"class":152},[38,1269,1270],{"class":156}," []) {\n",[38,1272,1273,1275,1277,1279,1281,1283,1285,1287,1289],{"class":40,"line":361},[38,1274,412],{"class":152},[38,1276,415],{"class":156},[38,1278,234],{"class":195},[38,1280,237],{"class":156},[38,1282,422],{"class":195},[38,1284,425],{"class":156},[38,1286,428],{"class":152},[38,1288,431],{"class":226},[38,1290,1291],{"class":156},"(asyncReducer, initialState)\n",[38,1293,1294],{"class":40,"line":367},[38,1295,58],{"emptyLinePlaceholder":57},[38,1297,1298,1301,1304,1306],{"class":40,"line":373},[38,1299,1300],{"class":226},"  useEffect",[38,1302,1303],{"class":156},"(() ",[38,1305,490],{"class":152},[38,1307,643],{"class":156},[38,1309,1310,1313,1316,1318],{"class":40,"line":381},[38,1311,1312],{"class":152},"    let",[38,1314,1315],{"class":156}," cancelled ",[38,1317,428],{"class":152},[38,1319,1320],{"class":195}," false\n",[38,1322,1323,1326,1328,1331],{"class":40,"line":387},[38,1324,1325],{"class":226},"    dispatch",[38,1327,496],{"class":156},[38,1329,1330],{"class":163},"'fetch_start'",[38,1332,1333],{"class":156}," })\n",[38,1335,1336,1339],{"class":40,"line":393},[38,1337,1338],{"class":226},"    fetchFn",[38,1340,1341],{"class":156},"()\n",[38,1343,1344,1347,1350,1352,1355,1358,1360,1363,1366,1369,1372,1374,1376,1379],{"class":40,"line":398},[38,1345,1346],{"class":156},"      .",[38,1348,1349],{"class":226},"then",[38,1351,230],{"class":156},[38,1353,1354],{"class":233},"data",[38,1356,1357],{"class":152}," =>",[38,1359,270],{"class":156},[38,1361,1362],{"class":152},"if",[38,1364,1365],{"class":156}," (",[38,1367,1368],{"class":152},"!",[38,1370,1371],{"class":156},"cancelled) ",[38,1373,422],{"class":226},[38,1375,496],{"class":156},[38,1377,1378],{"class":163},"'fetch_success'",[38,1380,1381],{"class":156},", data }) })\n",[38,1383,1384,1386,1389,1391,1394,1396,1398,1400,1402,1404,1406,1408,1410,1413],{"class":40,"line":409},[38,1385,1346],{"class":156},[38,1387,1388],{"class":226},"catch",[38,1390,230],{"class":156},[38,1392,1393],{"class":233},"error",[38,1395,1357],{"class":152},[38,1397,270],{"class":156},[38,1399,1362],{"class":152},[38,1401,1365],{"class":156},[38,1403,1368],{"class":152},[38,1405,1371],{"class":156},[38,1407,422],{"class":226},[38,1409,496],{"class":156},[38,1411,1412],{"class":163},"'fetch_error'",[38,1414,1415],{"class":156},", error: error.message }) })\n",[38,1417,1418,1421,1424,1426,1429,1431,1434],{"class":40,"line":437},[38,1419,1420],{"class":152},"    return",[38,1422,1423],{"class":156}," () ",[38,1425,490],{"class":152},[38,1427,1428],{"class":156}," { cancelled ",[38,1430,428],{"class":152},[38,1432,1433],{"class":195}," true",[38,1435,214],{"class":156},[38,1437,1438],{"class":40,"line":446},[38,1439,1440],{"class":156},"  }, deps)\n",[38,1442,1443],{"class":40,"line":459},[38,1444,58],{"emptyLinePlaceholder":57},[38,1446,1447,1449],{"class":40,"line":474},[38,1448,440],{"class":152},[38,1450,378],{"class":156},[38,1452,1453],{"class":40,"line":509},[38,1454,390],{"class":156},[38,1456,1457],{"class":40,"line":538},[38,1458,58],{"emptyLinePlaceholder":57},[38,1460,1461],{"class":40,"line":572},[38,1462,1463],{"class":44},"\u002F\u002F The state machine makes IMPOSSIBLE states unreachable:\n",[38,1465,1466],{"class":40,"line":601},[38,1467,1468],{"class":44},"\u002F\u002F You can't be in \"loading\" and \"success\" at the same time.\n",[38,1470,1471],{"class":40,"line":611},[38,1472,1473],{"class":44},"\u002F\u002F With separate useStates (setStatus, setData, setError), you CAN accidentally\n",[38,1475,1476],{"class":40,"line":617},[38,1477,1478],{"class":44},"\u002F\u002F set inconsistent combinations: setStatus('loading') + setData(oldData).\n",[20,1480,1482],{"id":1481},"lazy-initialization-with-usereducer","Lazy Initialization with useReducer",[25,1484,1486],{"filename":1485,"language":28},"lazy_init_reducer.js",[30,1487,1489],{"className":32,"code":1488,"language":28,"meta":34,"style":34},"\u002F\u002F useReducer supports lazy initialization (same as useState):\n\u002F\u002F useReducer(reducer, initialArg, initFunction)\n\u002F\u002F initFunction(initialArg) runs only on mount.\n\nfunction init(initialCount) {\n  \u002F\u002F Read from localStorage or compute on mount only\n  const saved = localStorage.getItem('count')\n  return { count: saved ? parseInt(saved, 10) : initialCount, step: 1 }\n}\n\nfunction Counter({ initialCount }) {\n  const [state, dispatch] = useReducer(reducer, initialCount, init)\n  \u002F\u002F init(initialCount) runs once on mount → no localStorage read on re-renders.\n  return \u003Cdiv>{state.count}\u003C\u002Fdiv>\n}\n",[15,1490,1491,1496,1501,1506,1510,1524,1529,1552,1584,1588,1592,1606,1627,1632,1647],{"__ignoreMap":34},[38,1492,1493],{"class":40,"line":41},[38,1494,1495],{"class":44},"\u002F\u002F useReducer supports lazy initialization (same as useState):\n",[38,1497,1498],{"class":40,"line":48},[38,1499,1500],{"class":44},"\u002F\u002F useReducer(reducer, initialArg, initFunction)\n",[38,1502,1503],{"class":40,"line":54},[38,1504,1505],{"class":44},"\u002F\u002F initFunction(initialArg) runs only on mount.\n",[38,1507,1508],{"class":40,"line":61},[38,1509,58],{"emptyLinePlaceholder":57},[38,1511,1512,1514,1517,1519,1522],{"class":40,"line":67},[38,1513,223],{"class":152},[38,1515,1516],{"class":226}," init",[38,1518,230],{"class":156},[38,1520,1521],{"class":233},"initialCount",[38,1523,243],{"class":156},[38,1525,1526],{"class":40,"line":73},[38,1527,1528],{"class":44},"  \u002F\u002F Read from localStorage or compute on mount only\n",[38,1530,1531,1533,1536,1538,1541,1544,1546,1549],{"class":40,"line":79},[38,1532,412],{"class":152},[38,1534,1535],{"class":195}," saved",[38,1537,199],{"class":152},[38,1539,1540],{"class":156}," localStorage.",[38,1542,1543],{"class":226},"getItem",[38,1545,230],{"class":156},[38,1547,1548],{"class":163},"'count'",[38,1550,1551],{"class":156},")\n",[38,1553,1554,1556,1559,1562,1565,1568,1571,1574,1577,1580,1582],{"class":40,"line":85},[38,1555,440],{"class":152},[38,1557,1558],{"class":156}," { count: saved ",[38,1560,1561],{"class":152},"?",[38,1563,1564],{"class":226}," parseInt",[38,1566,1567],{"class":156},"(saved, ",[38,1569,1570],{"class":195},"10",[38,1572,1573],{"class":156},") ",[38,1575,1576],{"class":152},":",[38,1578,1579],{"class":156}," initialCount, step: ",[38,1581,211],{"class":195},[38,1583,214],{"class":156},[38,1585,1586],{"class":40,"line":91},[38,1587,390],{"class":156},[38,1589,1590],{"class":40,"line":96},[38,1591,58],{"emptyLinePlaceholder":57},[38,1593,1594,1596,1598,1601,1603],{"class":40,"line":102},[38,1595,223],{"class":152},[38,1597,403],{"class":226},[38,1599,1600],{"class":156},"({ ",[38,1602,1521],{"class":233},[38,1604,1605],{"class":156}," }) {\n",[38,1607,1608,1610,1612,1614,1616,1618,1620,1622,1624],{"class":40,"line":108},[38,1609,412],{"class":152},[38,1611,415],{"class":156},[38,1613,234],{"class":195},[38,1615,237],{"class":156},[38,1617,422],{"class":195},[38,1619,425],{"class":156},[38,1621,428],{"class":152},[38,1623,431],{"class":226},[38,1625,1626],{"class":156},"(reducer, initialCount, init)\n",[38,1628,1629],{"class":40,"line":114},[38,1630,1631],{"class":44},"  \u002F\u002F init(initialCount) runs once on mount → no localStorage read on re-renders.\n",[38,1633,1634,1636,1639,1641,1643,1645],{"class":40,"line":120},[38,1635,440],{"class":152},[38,1637,1638],{"class":156}," \u003C",[38,1640,453],{"class":452},[38,1642,467],{"class":156},[38,1644,453],{"class":452},[38,1646,456],{"class":156},[38,1648,1649],{"class":40,"line":126},[38,1650,390],{"class":156},[20,1652,1654],{"id":1653},"tips-tricks","💡 Tips & Tricks",[25,1656,1658],{"filename":1657,"language":28},"tips.js",[30,1659,1661],{"className":32,"code":1660,"language":28,"meta":34,"style":34},"\u002F\u002F [Idiom] Extract the reducer and action creators into a separate file for\n\u002F\u002F testability. Reducers are PURE functions — you can unit test them in isolation\n\u002F\u002F without rendering any React components:\n\u002F\u002F   expect(reducer({ count: 0 }, { type: 'increment' })).toEqual({ count: 1 })\n\n\u002F\u002F [Idiom] Use a discriminated union for actions (TypeScript) to get autocomplete\n\u002F\u002F and exhaustive switch checking:\n\u002F\u002F   type Action = { type: 'increment' } | { type: 'setStep', step: number }\n\n\u002F\u002F [Debug] If state updates seem wrong, log every action:\n\u002F\u002F   function loggingReducer(state, action) { console.log(action); return realReducer(state, action) }\n\n\u002F\u002F [Idiom] For truly complex state, consider XState (finite state machine library)\n\u002F\u002F — it formalizes states, transitions, and guards. useReducer is a mini-FSM;\n\u002F\u002F XState is the full version when transitions have conditions and side effects.\n\n\u002F\u002F [Performance] dispatch is GUARANTEED stable (same as useState setters) — no\n\u002F\u002F need to useCallback it. Safe to pass dispatch directly to memoized children\n\u002F\u002F or include in dependency arrays without causing re-runs.\n",[15,1662,1663,1668,1673,1678,1683,1687,1692,1697,1702,1706,1711,1716,1720,1725,1730,1735,1739,1744,1749],{"__ignoreMap":34},[38,1664,1665],{"class":40,"line":41},[38,1666,1667],{"class":44},"\u002F\u002F [Idiom] Extract the reducer and action creators into a separate file for\n",[38,1669,1670],{"class":40,"line":48},[38,1671,1672],{"class":44},"\u002F\u002F testability. Reducers are PURE functions — you can unit test them in isolation\n",[38,1674,1675],{"class":40,"line":54},[38,1676,1677],{"class":44},"\u002F\u002F without rendering any React components:\n",[38,1679,1680],{"class":40,"line":61},[38,1681,1682],{"class":44},"\u002F\u002F   expect(reducer({ count: 0 }, { type: 'increment' })).toEqual({ count: 1 })\n",[38,1684,1685],{"class":40,"line":67},[38,1686,58],{"emptyLinePlaceholder":57},[38,1688,1689],{"class":40,"line":73},[38,1690,1691],{"class":44},"\u002F\u002F [Idiom] Use a discriminated union for actions (TypeScript) to get autocomplete\n",[38,1693,1694],{"class":40,"line":79},[38,1695,1696],{"class":44},"\u002F\u002F and exhaustive switch checking:\n",[38,1698,1699],{"class":40,"line":85},[38,1700,1701],{"class":44},"\u002F\u002F   type Action = { type: 'increment' } | { type: 'setStep', step: number }\n",[38,1703,1704],{"class":40,"line":91},[38,1705,58],{"emptyLinePlaceholder":57},[38,1707,1708],{"class":40,"line":96},[38,1709,1710],{"class":44},"\u002F\u002F [Debug] If state updates seem wrong, log every action:\n",[38,1712,1713],{"class":40,"line":102},[38,1714,1715],{"class":44},"\u002F\u002F   function loggingReducer(state, action) { console.log(action); return realReducer(state, action) }\n",[38,1717,1718],{"class":40,"line":108},[38,1719,58],{"emptyLinePlaceholder":57},[38,1721,1722],{"class":40,"line":114},[38,1723,1724],{"class":44},"\u002F\u002F [Idiom] For truly complex state, consider XState (finite state machine library)\n",[38,1726,1727],{"class":40,"line":120},[38,1728,1729],{"class":44},"\u002F\u002F — it formalizes states, transitions, and guards. useReducer is a mini-FSM;\n",[38,1731,1732],{"class":40,"line":126},[38,1733,1734],{"class":44},"\u002F\u002F XState is the full version when transitions have conditions and side effects.\n",[38,1736,1737],{"class":40,"line":132},[38,1738,58],{"emptyLinePlaceholder":57},[38,1740,1741],{"class":40,"line":329},[38,1742,1743],{"class":44},"\u002F\u002F [Performance] dispatch is GUARANTEED stable (same as useState setters) — no\n",[38,1745,1746],{"class":40,"line":339},[38,1747,1748],{"class":44},"\u002F\u002F need to useCallback it. Safe to pass dispatch directly to memoized children\n",[38,1750,1751],{"class":40,"line":347},[38,1752,1753],{"class":44},"\u002F\u002F or include in dependency arrays without causing re-runs.\n",[20,1755,1757],{"id":1756},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[25,1759,1761],{"filename":1760,"language":28},"edge_cases.js",[30,1762,1764],{"className":32,"code":1763,"language":28,"meta":34,"style":34},"\u002F\u002F [Gotcha] Reducers MUST be pure — no side effects, no async, no mutations.\n\u002F\u002F Don't fetch data, set timeouts, or dispatch from inside a reducer. Side effects\n\u002F\u002F belong in useEffect (triggered by state changes), not in the reducer itself.\n\n\u002F\u002F [Gotcha] Always return a NEW state object — never mutate the existing one.\n\u002F\u002F return state with a mutation → React sees the same reference → no re-render.\n\u002F\u002F return { ...state, count: state.count + 1 } → new object → re-render.\n\n\u002F\u002F [Gotcha] The default case in a switch should return state (not throw) for\n\u002F\u002F resilience, OR throw\u002Fassert for strictness. Silently returning state on\n\u002F\u002F unknown actions can mask typos in action type strings.\n\n\u002F\u002F [Gotcha] dispatch is stable but the state value changes. If you pass dispatch\n\u002F\u002F to a memoized child, it won't cause re-renders. But if you pass state, the\n\u002F\u002F child re-renders whenever state changes — even parts of state it doesn't use.\n\u002F\u002F Consider splitting state or using selectors (useSyncExternalStore).\n",[15,1765,1766,1771,1776,1781,1785,1790,1795,1800,1804,1809,1814,1819,1823,1828,1833,1838],{"__ignoreMap":34},[38,1767,1768],{"class":40,"line":41},[38,1769,1770],{"class":44},"\u002F\u002F [Gotcha] Reducers MUST be pure — no side effects, no async, no mutations.\n",[38,1772,1773],{"class":40,"line":48},[38,1774,1775],{"class":44},"\u002F\u002F Don't fetch data, set timeouts, or dispatch from inside a reducer. Side effects\n",[38,1777,1778],{"class":40,"line":54},[38,1779,1780],{"class":44},"\u002F\u002F belong in useEffect (triggered by state changes), not in the reducer itself.\n",[38,1782,1783],{"class":40,"line":61},[38,1784,58],{"emptyLinePlaceholder":57},[38,1786,1787],{"class":40,"line":67},[38,1788,1789],{"class":44},"\u002F\u002F [Gotcha] Always return a NEW state object — never mutate the existing one.\n",[38,1791,1792],{"class":40,"line":73},[38,1793,1794],{"class":44},"\u002F\u002F return state with a mutation → React sees the same reference → no re-render.\n",[38,1796,1797],{"class":40,"line":79},[38,1798,1799],{"class":44},"\u002F\u002F return { ...state, count: state.count + 1 } → new object → re-render.\n",[38,1801,1802],{"class":40,"line":85},[38,1803,58],{"emptyLinePlaceholder":57},[38,1805,1806],{"class":40,"line":91},[38,1807,1808],{"class":44},"\u002F\u002F [Gotcha] The default case in a switch should return state (not throw) for\n",[38,1810,1811],{"class":40,"line":96},[38,1812,1813],{"class":44},"\u002F\u002F resilience, OR throw\u002Fassert for strictness. Silently returning state on\n",[38,1815,1816],{"class":40,"line":102},[38,1817,1818],{"class":44},"\u002F\u002F unknown actions can mask typos in action type strings.\n",[38,1820,1821],{"class":40,"line":108},[38,1822,58],{"emptyLinePlaceholder":57},[38,1824,1825],{"class":40,"line":114},[38,1826,1827],{"class":44},"\u002F\u002F [Gotcha] dispatch is stable but the state value changes. If you pass dispatch\n",[38,1829,1830],{"class":40,"line":120},[38,1831,1832],{"class":44},"\u002F\u002F to a memoized child, it won't cause re-renders. But if you pass state, the\n",[38,1834,1835],{"class":40,"line":126},[38,1836,1837],{"class":44},"\u002F\u002F child re-renders whenever state changes — even parts of state it doesn't use.\n",[38,1839,1840],{"class":40,"line":132},[38,1841,1842],{"class":44},"\u002F\u002F Consider splitting state or using selectors (useSyncExternalStore).\n",[20,1844,1846],{"id":1845},"spot-the-bug","🧠 Spot the Bug",[1848,1849,1850],"p",{},"A reducer seems to \"lose\" other fields when updating one:",[25,1852,1854],{"filename":1853,"language":28},"spot_the_bug.js",[30,1855,1857],{"className":32,"code":1856,"language":28,"meta":34,"style":34},"function settingsReducer(state, action) {\n  switch (action.type) {\n    case 'setTheme':\n      return { theme: action.theme }  \u002F\u002F ← replaces the entire state object\n    case 'setFontSize':\n      return { fontSize: action.fontSize }  \u002F\u002F ← replaces again\n    default:\n      return state\n  }\n}\n",[15,1858,1859,1876,1882,1891,1901,1910,1920,1926,1932,1936],{"__ignoreMap":34},[38,1860,1861,1863,1866,1868,1870,1872,1874],{"class":40,"line":41},[38,1862,223],{"class":152},[38,1864,1865],{"class":226}," settingsReducer",[38,1867,230],{"class":156},[38,1869,234],{"class":233},[38,1871,237],{"class":156},[38,1873,240],{"class":233},[38,1875,243],{"class":156},[38,1877,1878,1880],{"class":40,"line":48},[38,1879,248],{"class":152},[38,1881,251],{"class":156},[38,1883,1884,1886,1889],{"class":40,"line":54},[38,1885,256],{"class":152},[38,1887,1888],{"class":163}," 'setTheme'",[38,1890,262],{"class":156},[38,1892,1893,1895,1898],{"class":40,"line":61},[38,1894,267],{"class":152},[38,1896,1897],{"class":156}," { theme: action.theme }  ",[38,1899,1900],{"class":44},"\u002F\u002F ← replaces the entire state object\n",[38,1902,1903,1905,1908],{"class":40,"line":67},[38,1904,256],{"class":152},[38,1906,1907],{"class":163}," 'setFontSize'",[38,1909,262],{"class":156},[38,1911,1912,1914,1917],{"class":40,"line":73},[38,1913,267],{"class":152},[38,1915,1916],{"class":156}," { fontSize: action.fontSize }  ",[38,1918,1919],{"class":44},"\u002F\u002F ← replaces again\n",[38,1921,1922,1924],{"class":40,"line":79},[38,1923,350],{"class":152},[38,1925,262],{"class":156},[38,1927,1928,1930],{"class":40,"line":85},[38,1929,267],{"class":152},[38,1931,378],{"class":156},[38,1933,1934],{"class":40,"line":91},[38,1935,384],{"class":156},[38,1937,1938],{"class":40,"line":96},[38,1939,390],{"class":156},[1941,1942,1943,1947,1975,1981,2025],"details",{},[1944,1945,1946],"summary",{},"Answer",[1848,1948,1949,1950,1954,1955,1958,1959,1962,1963,1966,1967,1970,1971,1974],{},"Each case returns a ",[1951,1952,1953],"strong",{},"new object"," with only one field — ",[15,1956,1957],{},"return { theme: action.theme }"," ",[1951,1960,1961],{},"replaces"," the entire state, losing ",[15,1964,1965],{},"fontSize"," (and any other fields). The next ",[15,1968,1969],{},"setFontSize"," then loses ",[15,1972,1973],{},"theme",".",[1848,1976,1977,1980],{},[1951,1978,1979],{},"Fix",": spread the previous state and override only the changed field:",[30,1982,1984],{"className":32,"code":1983,"language":28,"meta":34,"style":34},"case 'setTheme':\n  return { ...state, theme: action.theme }\ncase 'setFontSize':\n  return { ...state, fontSize: action.fontSize }\n",[15,1985,1986,1995,2006,2014],{"__ignoreMap":34},[38,1987,1988,1991,1993],{"class":40,"line":41},[38,1989,1990],{"class":152},"case",[38,1992,1888],{"class":163},[38,1994,262],{"class":156},[38,1996,1997,1999,2001,2003],{"class":40,"line":48},[38,1998,440],{"class":152},[38,2000,270],{"class":156},[38,2002,273],{"class":152},[38,2004,2005],{"class":156},"state, theme: action.theme }\n",[38,2007,2008,2010,2012],{"class":40,"line":54},[38,2009,1990],{"class":152},[38,2011,1907],{"class":163},[38,2013,262],{"class":156},[38,2015,2016,2018,2020,2022],{"class":40,"line":61},[38,2017,440],{"class":152},[38,2019,270],{"class":156},[38,2021,273],{"class":152},[38,2023,2024],{"class":156},"state, fontSize: action.fontSize }\n",[1848,2026,2027,2028,2031],{},"The spread preserves all other fields while updating only the target. This is the same pattern as ",[15,2029,2030],{},"useState"," with object state (Chapter 4) — always create a new object with the previous fields merged in.",[20,2033,2035],{"id":2034},"key-takeaways","Key Takeaways",[25,2037,2039],{"filename":2038,"language":28},"key_takeaways.js",[30,2040,2042],{"className":32,"code":2041,"language":28,"meta":34,"style":34},"\u002F\u002F 1. useReducer over useState when: state has interdependent fields, complex\n\u002F\u002F    transitions, many fields, or a finite set of \"modes.\" Reducers are pure\n\u002F\u002F    functions: (state, action) → newState, no side effects.\n\n\u002F\u002F 2. Reducers are testable in isolation — no React rendering needed. Extract to\n\u002F\u002F    a separate file for unit testing state transitions.\n\n\u002F\u002F 3. Always spread previous state in the reducer: { ...state, changedField: value }.\n\u002F\u002F    Returning a partial object replaces the entire state — other fields are lost.\n\n\u002F\u002F 4. State machines (idle → loading → success\u002Ferror) make impossible states\n\u002F\u002F    unreachable. With separate useStates, you can accidentally set inconsistent\n\u002F\u002F    combinations (loading=true + data=stale). A reducer prevents this by design.\n\n\u002F\u002F 5. dispatch is guaranteed stable (like useState setters) — safe in dependency\n\u002F\u002F    arrays and memoized children. Lazy initialization: useReducer(reducer, arg, initFn).\n",[15,2043,2044,2049,2054,2059,2063,2068,2073,2077,2082,2087,2091,2096,2101,2106,2110,2115],{"__ignoreMap":34},[38,2045,2046],{"class":40,"line":41},[38,2047,2048],{"class":44},"\u002F\u002F 1. useReducer over useState when: state has interdependent fields, complex\n",[38,2050,2051],{"class":40,"line":48},[38,2052,2053],{"class":44},"\u002F\u002F    transitions, many fields, or a finite set of \"modes.\" Reducers are pure\n",[38,2055,2056],{"class":40,"line":54},[38,2057,2058],{"class":44},"\u002F\u002F    functions: (state, action) → newState, no side effects.\n",[38,2060,2061],{"class":40,"line":61},[38,2062,58],{"emptyLinePlaceholder":57},[38,2064,2065],{"class":40,"line":67},[38,2066,2067],{"class":44},"\u002F\u002F 2. Reducers are testable in isolation — no React rendering needed. Extract to\n",[38,2069,2070],{"class":40,"line":73},[38,2071,2072],{"class":44},"\u002F\u002F    a separate file for unit testing state transitions.\n",[38,2074,2075],{"class":40,"line":79},[38,2076,58],{"emptyLinePlaceholder":57},[38,2078,2079],{"class":40,"line":85},[38,2080,2081],{"class":44},"\u002F\u002F 3. Always spread previous state in the reducer: { ...state, changedField: value }.\n",[38,2083,2084],{"class":40,"line":91},[38,2085,2086],{"class":44},"\u002F\u002F    Returning a partial object replaces the entire state — other fields are lost.\n",[38,2088,2089],{"class":40,"line":96},[38,2090,58],{"emptyLinePlaceholder":57},[38,2092,2093],{"class":40,"line":102},[38,2094,2095],{"class":44},"\u002F\u002F 4. State machines (idle → loading → success\u002Ferror) make impossible states\n",[38,2097,2098],{"class":40,"line":108},[38,2099,2100],{"class":44},"\u002F\u002F    unreachable. With separate useStates, you can accidentally set inconsistent\n",[38,2102,2103],{"class":40,"line":114},[38,2104,2105],{"class":44},"\u002F\u002F    combinations (loading=true + data=stale). A reducer prevents this by design.\n",[38,2107,2108],{"class":40,"line":120},[38,2109,58],{"emptyLinePlaceholder":57},[38,2111,2112],{"class":40,"line":126},[38,2113,2114],{"class":44},"\u002F\u002F 5. dispatch is guaranteed stable (like useState setters) — safe in dependency\n",[38,2116,2117],{"class":40,"line":132},[38,2118,2119],{"class":44},"\u002F\u002F    arrays and memoized children. Lazy initialization: useReducer(reducer, arg, initFn).\n",[2121,2122,2123],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}",{"title":34,"searchDepth":48,"depth":48,"links":2125},[2126,2127,2128,2129,2130,2131,2132,2133,2134],{"id":22,"depth":48,"text":23},{"id":138,"depth":48,"text":139},{"id":622,"depth":48,"text":623},{"id":1060,"depth":48,"text":1061},{"id":1481,"depth":48,"text":1482},{"id":1653,"depth":48,"text":1654},{"id":1756,"depth":48,"text":1757},{"id":1845,"depth":48,"text":1846},{"id":2034,"depth":48,"text":2035},"useReducer mechanics, action dispatch, reducer composition, when to choose useReducer over useState, and production patterns for multi-field forms and state machines. Code-first reference for mid-to-senior React engineers.","md",{},"\u002Freact\u002F10-usereducer-and-complex-state",{"title":5,"description":2135},"react\u002F10-usereducer-and-complex-state","8R4QnlwGGWlQFlewmN9cb8xIPraEJ_iOTS3X5r-6ANc",1789924652157]