[{"data":1,"prerenderedAt":1747},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-usestate-hook-complete-guide":3},{"id":4,"title":5,"body":6,"description":1733,"difficulty":1734,"extension":1735,"framework":1736,"frameworkSlug":1737,"meta":1738,"navigation":317,"order":56,"path":1739,"qaPath":1740,"seo":1741,"stem":1742,"subtopic":20,"topic":1743,"topicSlug":1744,"updated":1745,"__hash__":1746},"blog\u002Fblog\u002Freact-usestate-hook-complete-guide.md","React useState Hook — A Complete Guide with Examples",{"type":7,"value":8,"toc":1716},"minimark",[9,14,25,29,43,101,115,143,147,162,231,249,252,256,261,267,386,393,498,502,509,567,585,589,598,673,698,702,724,788,799,917,920,953,960,1032,1036,1046,1145,1152,1186,1196,1200,1234,1314,1317,1321,1328,1433,1444,1473,1483,1487,1490,1554,1585,1589,1637,1671,1675,1712],[10,11,13],"h2",{"id":12},"a-complete-guide-to-usestate","A complete guide to useState",[15,16,17,21,22,24],"p",{},[18,19,20],"code",{},"useState"," is the first hook every React developer learns, and also the one that\nhides the most subtle behavior. On the surface it just stores a value and gives you\na way to change it. Underneath, it ties into how React renders, how JavaScript\nclosures capture values, and how updates are batched and scheduled. This guide\nwalks through all of it — from the basics to the edge cases interviewers love — so\nthat by the end you can reason confidently about any ",[18,23,20],{}," question.",[10,26,28],{"id":27},"the-shape-of-the-hook","The shape of the hook",[15,30,31,33,34,38,39,42],{},[18,32,20],{}," returns an array of exactly two elements: the ",[35,36,37],"strong",{},"current value"," for this\nrender and a ",[35,40,41],{},"setter"," that schedules an update.",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-jsx shiki shiki-themes github-light github-dark","const [count, setCount] = useState(0)\n\u002F\u002F     ▲ value for this render   ▲ asks React to re-render with a new value\n","jsx","",[18,51,52,94],{"__ignoreMap":49},[53,54,57,61,65,69,72,75,78,81,85,88,91],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"szBVR","const",[53,62,64],{"class":63},"sVt8B"," [",[53,66,68],{"class":67},"sj4cs","count",[53,70,71],{"class":63},", ",[53,73,74],{"class":67},"setCount",[53,76,77],{"class":63},"] ",[53,79,80],{"class":59},"=",[53,82,84],{"class":83},"sScJk"," useState",[53,86,87],{"class":63},"(",[53,89,90],{"class":67},"0",[53,92,93],{"class":63},")\n",[53,95,97],{"class":55,"line":96},2,[53,98,100],{"class":99},"sJ8bj","\u002F\u002F     ▲ value for this render   ▲ asks React to re-render with a new value\n",[15,102,103,104,107,108,110,111,114],{},"You destructure them into a ",[18,105,106],{},"[value, setValue]"," pair — a naming convention, not a\nrule React enforces. The argument you pass (",[18,109,90],{},") is only the ",[35,112,113],{},"initial"," value: it's\nused on the very first render and ignored on every render afterward, when React\nhands you the latest stored value instead.",[15,116,117,118,121,122,125,126,129,130,133,134,137,138,142],{},"Two facts about the setter are worth committing to memory. First, its ",[35,119,120],{},"identity is\nstable"," — React guarantees the same setter function across renders, so it's safe to\nleave out of ",[18,123,124],{},"useEffect"," or ",[18,127,128],{},"useCallback"," dependency arrays. Second, calling it does\n",[35,131,132],{},"not"," change the ",[18,135,136],{},"value"," variable in your current scope; it schedules a ",[139,140,141],"em",{},"future","\nrender.",[10,144,146],{"id":145},"state-is-a-snapshot-not-a-live-variable","State is a snapshot, not a live variable",[15,148,149,150,152,153,156,158,159,161],{},"This is the single most important idea in ",[18,151,20],{},". Each render gets its ",[35,154,155],{},"own",[18,157,68],{}," constant, frozen at the value it had when that render ran. The setter can't\nreach back and mutate the ",[18,160,68],{}," you're currently looking at.",[44,163,165],{"className":46,"code":164,"language":48,"meta":49,"style":49},"function handleClick() {\n  console.log(count)   \u002F\u002F e.g. 0\n  setCount(count + 1)  \u002F\u002F schedules a render where count will be 1\n  console.log(count)   \u002F\u002F STILL 0 — same render, same frozen constant\n}\n",[18,166,167,178,192,213,225],{"__ignoreMap":49},[53,168,169,172,175],{"class":55,"line":56},[53,170,171],{"class":59},"function",[53,173,174],{"class":83}," handleClick",[53,176,177],{"class":63},"() {\n",[53,179,180,183,186,189],{"class":55,"line":96},[53,181,182],{"class":63},"  console.",[53,184,185],{"class":83},"log",[53,187,188],{"class":63},"(count)   ",[53,190,191],{"class":99},"\u002F\u002F e.g. 0\n",[53,193,195,198,201,204,207,210],{"class":55,"line":194},3,[53,196,197],{"class":83},"  setCount",[53,199,200],{"class":63},"(count ",[53,202,203],{"class":59},"+",[53,205,206],{"class":67}," 1",[53,208,209],{"class":63},")  ",[53,211,212],{"class":99},"\u002F\u002F schedules a render where count will be 1\n",[53,214,216,218,220,222],{"class":55,"line":215},4,[53,217,182],{"class":63},[53,219,185],{"class":83},[53,221,188],{"class":63},[53,223,224],{"class":99},"\u002F\u002F STILL 0 — same render, same frozen constant\n",[53,226,228],{"class":55,"line":227},5,[53,229,230],{"class":63},"}\n",[15,232,233,234,237,238,241,242,245,246,248],{},"People describe this as state being \"one render behind,\" but that framing is\nmisleading. You're not behind — you're reading a ",[35,235,236],{},"snapshot",". The new value only\nbecomes visible in the ",[139,239,240],{},"next"," render's function body. If you need the updated value\nright away inside the same handler, compute it locally (",[18,243,244],{},"const next = count + 1",") and\nuse ",[18,247,240],{},".",[15,250,251],{},"This snapshot model is a direct consequence of JavaScript closures: every function\ndefined during a render closes over that render's variables. It explains stale\nvalues in timers, effects, and async callbacks — all of which capture the value from\nthe render in which they were created.",[10,253,255],{"id":254},"updating-state-correctly","Updating state correctly",[257,258,260],"h3",{"id":259},"functional-updates","Functional updates",[15,262,263,264,266],{},"Whenever the next state depends on the previous state, pass a ",[35,265,171],{}," to the\nsetter instead of a value. React calls your updater with the most up-to-date state,\nso you never compute from a stale snapshot.",[44,268,270],{"className":46,"code":269,"language":48,"meta":49,"style":49},"\u002F\u002F all three read the same stale `count`, so this adds 1, not 3\nsetCount(count + 1)\nsetCount(count + 1)\nsetCount(count + 1)\n\n\u002F\u002F each updater receives the result of the previous one -> +3\nsetCount(c => c + 1)\nsetCount(c => c + 1)\nsetCount(c => c + 1)\n",[18,271,272,277,289,301,313,319,325,348,367],{"__ignoreMap":49},[53,273,274],{"class":55,"line":56},[53,275,276],{"class":99},"\u002F\u002F all three read the same stale `count`, so this adds 1, not 3\n",[53,278,279,281,283,285,287],{"class":55,"line":96},[53,280,74],{"class":83},[53,282,200],{"class":63},[53,284,203],{"class":59},[53,286,206],{"class":67},[53,288,93],{"class":63},[53,290,291,293,295,297,299],{"class":55,"line":194},[53,292,74],{"class":83},[53,294,200],{"class":63},[53,296,203],{"class":59},[53,298,206],{"class":67},[53,300,93],{"class":63},[53,302,303,305,307,309,311],{"class":55,"line":215},[53,304,74],{"class":83},[53,306,200],{"class":63},[53,308,203],{"class":59},[53,310,206],{"class":67},[53,312,93],{"class":63},[53,314,315],{"class":55,"line":227},[53,316,318],{"emptyLinePlaceholder":317},true,"\n",[53,320,322],{"class":55,"line":321},6,[53,323,324],{"class":99},"\u002F\u002F each updater receives the result of the previous one -> +3\n",[53,326,328,330,332,336,339,342,344,346],{"class":55,"line":327},7,[53,329,74],{"class":83},[53,331,87],{"class":63},[53,333,335],{"class":334},"s4XuR","c",[53,337,338],{"class":59}," =>",[53,340,341],{"class":63}," c ",[53,343,203],{"class":59},[53,345,206],{"class":67},[53,347,93],{"class":63},[53,349,351,353,355,357,359,361,363,365],{"class":55,"line":350},8,[53,352,74],{"class":83},[53,354,87],{"class":63},[53,356,335],{"class":334},[53,358,338],{"class":59},[53,360,341],{"class":63},[53,362,203],{"class":59},[53,364,206],{"class":67},[53,366,93],{"class":63},[53,368,370,372,374,376,378,380,382,384],{"class":55,"line":369},9,[53,371,74],{"class":83},[53,373,87],{"class":63},[53,375,335],{"class":334},[53,377,338],{"class":59},[53,379,341],{"class":63},[53,381,203],{"class":59},[53,383,206],{"class":67},[53,385,93],{"class":63},[15,387,388,389,392],{},"The rule of thumb: if the new value mentions the old value, use the function form.\nFor an unrelated value (",[18,390,391],{},"setCount(0)","), the direct form is fine. The functional form\nis also what keeps toggles and async handlers correct:",[44,394,396],{"className":46,"code":395,"language":48,"meta":49,"style":49},"const toggle = () => setOpen(o => !o)       \u002F\u002F always flips the latest value\n\nasync function save() {\n  await delay(1000)\n  setCount(latest => { send(latest); return latest }) \u002F\u002F read the freshest value\n}\n",[18,397,398,433,437,450,465,494],{"__ignoreMap":49},[53,399,400,402,405,408,411,414,417,419,422,424,427,430],{"class":55,"line":56},[53,401,60],{"class":59},[53,403,404],{"class":83}," toggle",[53,406,407],{"class":59}," =",[53,409,410],{"class":63}," () ",[53,412,413],{"class":59},"=>",[53,415,416],{"class":83}," setOpen",[53,418,87],{"class":63},[53,420,421],{"class":334},"o",[53,423,338],{"class":59},[53,425,426],{"class":59}," !",[53,428,429],{"class":63},"o)       ",[53,431,432],{"class":99},"\u002F\u002F always flips the latest value\n",[53,434,435],{"class":55,"line":96},[53,436,318],{"emptyLinePlaceholder":317},[53,438,439,442,445,448],{"class":55,"line":194},[53,440,441],{"class":59},"async",[53,443,444],{"class":59}," function",[53,446,447],{"class":83}," save",[53,449,177],{"class":63},[53,451,452,455,458,460,463],{"class":55,"line":215},[53,453,454],{"class":59},"  await",[53,456,457],{"class":83}," delay",[53,459,87],{"class":63},[53,461,462],{"class":67},"1000",[53,464,93],{"class":63},[53,466,467,469,471,474,476,479,482,485,488,491],{"class":55,"line":227},[53,468,197],{"class":83},[53,470,87],{"class":63},[53,472,473],{"class":334},"latest",[53,475,338],{"class":59},[53,477,478],{"class":63}," { ",[53,480,481],{"class":83},"send",[53,483,484],{"class":63},"(latest); ",[53,486,487],{"class":59},"return",[53,489,490],{"class":63}," latest }) ",[53,492,493],{"class":99},"\u002F\u002F read the freshest value\n",[53,495,496],{"class":55,"line":321},[53,497,230],{"class":63},[257,499,501],{"id":500},"batching","Batching",[15,503,504,505,508],{},"React groups multiple state updates that happen in the same event into a ",[35,506,507],{},"single\nre-render"," for performance. Three setters in one click handler produce one render,\nnot three.",[44,510,512],{"className":46,"code":511,"language":48,"meta":49,"style":49},"function handleClick() {\n  setA(1)\n  setB(2)\n  setC(3)\n  \u002F\u002F ONE re-render\n}\n",[18,513,514,522,534,546,558,563],{"__ignoreMap":49},[53,515,516,518,520],{"class":55,"line":56},[53,517,171],{"class":59},[53,519,174],{"class":83},[53,521,177],{"class":63},[53,523,524,527,529,532],{"class":55,"line":96},[53,525,526],{"class":83},"  setA",[53,528,87],{"class":63},[53,530,531],{"class":67},"1",[53,533,93],{"class":63},[53,535,536,539,541,544],{"class":55,"line":194},[53,537,538],{"class":83},"  setB",[53,540,87],{"class":63},[53,542,543],{"class":67},"2",[53,545,93],{"class":63},[53,547,548,551,553,556],{"class":55,"line":215},[53,549,550],{"class":83},"  setC",[53,552,87],{"class":63},[53,554,555],{"class":67},"3",[53,557,93],{"class":63},[53,559,560],{"class":55,"line":227},[53,561,562],{"class":99},"  \u002F\u002F ONE re-render\n",[53,564,565],{"class":55,"line":321},[53,566,230],{"class":63},[15,568,569,570,573,574,577,578,581,582,248],{},"Before React 18, batching only applied inside React event handlers; updates in\n",[18,571,572],{},"setTimeout",", promises, or native listeners each caused their own render. ",[35,575,576],{},"React 18\nautomatic batching"," extends grouping to those async contexts too. On the rare\noccasion you need a synchronous, separate render (for example, to measure the DOM\nbetween two updates), wrap an update in ",[18,579,580],{},"flushSync"," from ",[18,583,584],{},"react-dom",[10,586,588],{"id":587},"lazy-initialization","Lazy initialization",[15,590,591,592,594,595,597],{},"If computing the initial value is expensive, pass a ",[35,593,171],{}," to ",[18,596,20],{},".\nReact calls it only on the first render and ignores it afterward.",[44,599,601],{"className":46,"code":600,"language":48,"meta":49,"style":49},"\u002F\u002F readFromStorage() runs on every render, then the result is thrown away\nconst [items, setItems] = useState(readFromStorage())\n\n\u002F\u002F runs once, on mount only\nconst [items, setItems] = useState(() => readFromStorage())\n",[18,602,603,608,636,640,645],{"__ignoreMap":49},[53,604,605],{"class":55,"line":56},[53,606,607],{"class":99},"\u002F\u002F readFromStorage() runs on every render, then the result is thrown away\n",[53,609,610,612,614,617,619,622,624,626,628,630,633],{"class":55,"line":96},[53,611,60],{"class":59},[53,613,64],{"class":63},[53,615,616],{"class":67},"items",[53,618,71],{"class":63},[53,620,621],{"class":67},"setItems",[53,623,77],{"class":63},[53,625,80],{"class":59},[53,627,84],{"class":83},[53,629,87],{"class":63},[53,631,632],{"class":83},"readFromStorage",[53,634,635],{"class":63},"())\n",[53,637,638],{"class":55,"line":194},[53,639,318],{"emptyLinePlaceholder":317},[53,641,642],{"class":55,"line":215},[53,643,644],{"class":99},"\u002F\u002F runs once, on mount only\n",[53,646,647,649,651,653,655,657,659,661,663,666,668,671],{"class":55,"line":227},[53,648,60],{"class":59},[53,650,64],{"class":63},[53,652,616],{"class":67},[53,654,71],{"class":63},[53,656,621],{"class":67},[53,658,77],{"class":63},[53,660,80],{"class":59},[53,662,84],{"class":83},[53,664,665],{"class":63},"(() ",[53,667,413],{"class":59},[53,669,670],{"class":83}," readFromStorage",[53,672,635],{"class":63},[15,674,675,676,679,680,679,683,686,687,690,691,694,695,248],{},"Watch the distinction carefully: ",[18,677,678],{},"useState(expensive())"," ",[139,681,682],{},"calls",[18,684,685],{},"expensive"," every\nrender (its return value is the argument), while ",[18,688,689],{},"useState(() => expensive())"," hands\nReact a function to call once. The same wrapping trick is how you ",[35,692,693],{},"store a function\nin state"," — wrap it so the setter saves it instead of treating it as an updater:\n",[18,696,697],{},"useState(() => myFn)",[10,699,701],{"id":700},"working-with-objects-and-arrays","Working with objects and arrays",[15,703,704,705,707,708,711,712,715,716,719,720,723],{},"The ",[18,706,20],{}," setter ",[35,709,710],{},"replaces"," the value — unlike the old class ",[18,713,714],{},"this.setState",",\nit does not shallow-merge. And React decides whether to re-render by comparing\n",[35,717,718],{},"references",", so you must always produce a ",[35,721,722],{},"new"," object or array; mutating the\nexisting one in place won't trigger a render.",[44,725,727],{"className":46,"code":726,"language":48,"meta":49,"style":49},"\u002F\u002F new object, old fields preserved\nsetUser(u => ({ ...u, name: 'Grace' }))\n\n\u002F\u002F same reference — React skips the re-render\nuser.name = 'Grace'\nsetUser(user)\n",[18,728,729,734,762,766,771,781],{"__ignoreMap":49},[53,730,731],{"class":55,"line":56},[53,732,733],{"class":99},"\u002F\u002F new object, old fields preserved\n",[53,735,736,739,741,744,746,749,752,755,759],{"class":55,"line":96},[53,737,738],{"class":83},"setUser",[53,740,87],{"class":63},[53,742,743],{"class":334},"u",[53,745,338],{"class":59},[53,747,748],{"class":63}," ({ ",[53,750,751],{"class":59},"...",[53,753,754],{"class":63},"u, name: ",[53,756,758],{"class":757},"sZZnC","'Grace'",[53,760,761],{"class":63}," }))\n",[53,763,764],{"class":55,"line":194},[53,765,318],{"emptyLinePlaceholder":317},[53,767,768],{"class":55,"line":215},[53,769,770],{"class":99},"\u002F\u002F same reference — React skips the re-render\n",[53,772,773,776,778],{"class":55,"line":227},[53,774,775],{"class":63},"user.name ",[53,777,80],{"class":59},[53,779,780],{"class":757}," 'Grace'\n",[53,782,783,785],{"class":55,"line":321},[53,784,738],{"class":83},[53,786,787],{"class":63},"(user)\n",[15,789,790,791,794,795,798],{},"Arrays follow the same principle — never ",[18,792,793],{},"push","\u002F",[18,796,797],{},"splice","; build a new array:",[44,800,802],{"className":46,"code":801,"language":48,"meta":49,"style":49},"setItems(prev => [...prev, newItem])                 \u002F\u002F add\nsetItems(prev => prev.filter(it => it.id !== id))    \u002F\u002F remove\nsetItems(prev => prev.map(it =>                       \u002F\u002F update one item\n  it.id === id ? { ...it, done: true } : it))\n",[18,803,804,825,860,884],{"__ignoreMap":49},[53,805,806,808,810,813,815,817,819,822],{"class":55,"line":56},[53,807,621],{"class":83},[53,809,87],{"class":63},[53,811,812],{"class":334},"prev",[53,814,338],{"class":59},[53,816,64],{"class":63},[53,818,751],{"class":59},[53,820,821],{"class":63},"prev, newItem])                 ",[53,823,824],{"class":99},"\u002F\u002F add\n",[53,826,827,829,831,833,835,838,841,843,846,848,851,854,857],{"class":55,"line":96},[53,828,621],{"class":83},[53,830,87],{"class":63},[53,832,812],{"class":334},[53,834,338],{"class":59},[53,836,837],{"class":63}," prev.",[53,839,840],{"class":83},"filter",[53,842,87],{"class":63},[53,844,845],{"class":334},"it",[53,847,338],{"class":59},[53,849,850],{"class":63}," it.id ",[53,852,853],{"class":59},"!==",[53,855,856],{"class":63}," id))    ",[53,858,859],{"class":99},"\u002F\u002F remove\n",[53,861,862,864,866,868,870,872,875,877,879,881],{"class":55,"line":194},[53,863,621],{"class":83},[53,865,87],{"class":63},[53,867,812],{"class":334},[53,869,338],{"class":59},[53,871,837],{"class":63},[53,873,874],{"class":83},"map",[53,876,87],{"class":63},[53,878,845],{"class":334},[53,880,338],{"class":59},[53,882,883],{"class":99},"                       \u002F\u002F update one item\n",[53,885,886,889,892,895,898,900,902,905,908,911,914],{"class":55,"line":215},[53,887,888],{"class":63},"  it.id ",[53,890,891],{"class":59},"===",[53,893,894],{"class":63}," id ",[53,896,897],{"class":59},"?",[53,899,478],{"class":63},[53,901,751],{"class":59},[53,903,904],{"class":63},"it, done: ",[53,906,907],{"class":67},"true",[53,909,910],{"class":63}," } ",[53,912,913],{"class":59},":",[53,915,916],{"class":63}," it))\n",[15,918,919],{},"For deeply nested state you must spread at every level you change, which gets\nverbose:",[44,921,923],{"className":46,"code":922,"language":48,"meta":49,"style":49},"setUser(prev => ({ ...prev, address: { ...prev.address, city: 'Paris' } }))\n",[18,924,925],{"__ignoreMap":49},[53,926,927,929,931,933,935,937,939,942,944,947,950],{"class":55,"line":56},[53,928,738],{"class":83},[53,930,87],{"class":63},[53,932,812],{"class":334},[53,934,338],{"class":59},[53,936,748],{"class":63},[53,938,751],{"class":59},[53,940,941],{"class":63},"prev, address: { ",[53,943,751],{"class":59},[53,945,946],{"class":63},"prev.address, city: ",[53,948,949],{"class":757},"'Paris'",[53,951,952],{"class":63}," } }))\n",[15,954,955,956,959],{},"When this spreading becomes painful, that's the signal to flatten your state, reach\nfor ",[18,957,958],{},"useReducer",", or use a library like Immer. The same pattern powers a generic\nform handler, where a computed key updates exactly one field:",[44,961,963],{"className":46,"code":962,"language":48,"meta":49,"style":49},"const [form, setForm] = useState({ name: '', email: '' })\nconst onChange = e =>\n  setForm(prev => ({ ...prev, [e.target.name]: e.target.value }))\n",[18,964,965,999,1014],{"__ignoreMap":49},[53,966,967,969,971,974,976,979,981,983,985,988,991,994,996],{"class":55,"line":56},[53,968,60],{"class":59},[53,970,64],{"class":63},[53,972,973],{"class":67},"form",[53,975,71],{"class":63},[53,977,978],{"class":67},"setForm",[53,980,77],{"class":63},[53,982,80],{"class":59},[53,984,84],{"class":83},[53,986,987],{"class":63},"({ name: ",[53,989,990],{"class":757},"''",[53,992,993],{"class":63},", email: ",[53,995,990],{"class":757},[53,997,998],{"class":63}," })\n",[53,1000,1001,1003,1006,1008,1011],{"class":55,"line":96},[53,1002,60],{"class":59},[53,1004,1005],{"class":83}," onChange",[53,1007,407],{"class":59},[53,1009,1010],{"class":334}," e",[53,1012,1013],{"class":59}," =>\n",[53,1015,1016,1019,1021,1023,1025,1027,1029],{"class":55,"line":194},[53,1017,1018],{"class":83},"  setForm",[53,1020,87],{"class":63},[53,1022,812],{"class":334},[53,1024,338],{"class":59},[53,1026,748],{"class":63},[53,1028,751],{"class":59},[53,1030,1031],{"class":63},"prev, [e.target.name]: e.target.value }))\n",[10,1033,1035],{"id":1034},"choose-the-minimum-state","Choose the minimum state",[15,1037,1038,1039,1042,1043,1045],{},"A common source of bugs is storing data that you could ",[35,1040,1041],{},"derive",". If a value can be\ncomputed from existing state or props, compute it during render instead of keeping a\nsecond copy in ",[18,1044,20],{}," that can drift out of sync.",[44,1047,1049],{"className":46,"code":1048,"language":48,"meta":49,"style":49},"\u002F\u002F redundant — must be kept in sync by hand\nconst [items, setItems] = useState([])\nconst [count, setCount] = useState(0)\n\n\u002F\u002F derive it\nconst [items, setItems] = useState([])\nconst count = items.length\n",[18,1050,1051,1056,1077,1101,1105,1110,1130],{"__ignoreMap":49},[53,1052,1053],{"class":55,"line":56},[53,1054,1055],{"class":99},"\u002F\u002F redundant — must be kept in sync by hand\n",[53,1057,1058,1060,1062,1064,1066,1068,1070,1072,1074],{"class":55,"line":96},[53,1059,60],{"class":59},[53,1061,64],{"class":63},[53,1063,616],{"class":67},[53,1065,71],{"class":63},[53,1067,621],{"class":67},[53,1069,77],{"class":63},[53,1071,80],{"class":59},[53,1073,84],{"class":83},[53,1075,1076],{"class":63},"([])\n",[53,1078,1079,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099],{"class":55,"line":194},[53,1080,60],{"class":59},[53,1082,64],{"class":63},[53,1084,68],{"class":67},[53,1086,71],{"class":63},[53,1088,74],{"class":67},[53,1090,77],{"class":63},[53,1092,80],{"class":59},[53,1094,84],{"class":83},[53,1096,87],{"class":63},[53,1098,90],{"class":67},[53,1100,93],{"class":63},[53,1102,1103],{"class":55,"line":215},[53,1104,318],{"emptyLinePlaceholder":317},[53,1106,1107],{"class":55,"line":227},[53,1108,1109],{"class":99},"\u002F\u002F derive it\n",[53,1111,1112,1114,1116,1118,1120,1122,1124,1126,1128],{"class":55,"line":321},[53,1113,60],{"class":59},[53,1115,64],{"class":63},[53,1117,616],{"class":67},[53,1119,71],{"class":63},[53,1121,621],{"class":67},[53,1123,77],{"class":63},[53,1125,80],{"class":59},[53,1127,84],{"class":83},[53,1129,1076],{"class":63},[53,1131,1132,1134,1137,1139,1142],{"class":55,"line":327},[53,1133,60],{"class":59},[53,1135,1136],{"class":67}," count",[53,1138,407],{"class":59},[53,1140,1141],{"class":63}," items.",[53,1143,1144],{"class":67},"length\n",[15,1146,1147,1148,1151],{},"If the derivation is genuinely expensive, cache it with ",[18,1149,1150],{},"useMemo"," rather than storing\nit in state — you keep a single source of truth while avoiding recomputation:",[44,1153,1155],{"className":46,"code":1154,"language":48,"meta":49,"style":49},"const sorted = useMemo(() => [...items].sort(compare), [items])\n",[18,1156,1157],{"__ignoreMap":49},[53,1158,1159,1161,1164,1166,1169,1171,1173,1175,1177,1180,1183],{"class":55,"line":56},[53,1160,60],{"class":59},[53,1162,1163],{"class":67}," sorted",[53,1165,407],{"class":59},[53,1167,1168],{"class":83}," useMemo",[53,1170,665],{"class":63},[53,1172,413],{"class":59},[53,1174,64],{"class":63},[53,1176,751],{"class":59},[53,1178,1179],{"class":63},"items].",[53,1181,1182],{"class":83},"sort",[53,1184,1185],{"class":63},"(compare), [items])\n",[15,1187,1188,1189,1192,1193,1195],{},"Keep state ",[35,1190,1191],{},"minimal, orthogonal, and colocated",": the smallest set of independent\nvalues, placed as close as possible to where they're used. Prefer several\nindependent ",[18,1194,20],{}," calls over one big object when the fields change separately —\nyou avoid the spread-merge on every update.",[10,1197,1199],{"id":1198},"usestate-vs-useref-vs-usereducer","useState vs useRef vs useReducer",[1201,1202,1203,1212,1227],"ul",{},[1204,1205,1206,1207,1211],"li",{},"Use ",[35,1208,1209],{},[18,1210,20],{}," for values that should trigger a re-render when they change.",[1204,1213,1206,1214,1219,1220,1222,1223,1226],{},[35,1215,1216],{},[18,1217,1218],{},"useRef"," for mutable values that persist across renders but should ",[35,1221,132],{},"\ncause a render — timer ids, the previous value, a DOM node. Changing ",[18,1224,1225],{},"ref.current","\nis invisible to React's render cycle.",[1204,1228,1206,1229,1233],{},[35,1230,1231],{},[18,1232,958],{}," when state is complex, several values change together, or you\nwant to centralize update logic in one tested function instead of scattering\nsetters.",[44,1235,1237],{"className":46,"code":1236,"language":48,"meta":49,"style":49},"const renders = useRef(0)        \u002F\u002F bookkeeping, no re-render\nrenders.current++\n\nconst [state, dispatch] = useReducer(reducer, initial)\ndispatch({ type: 'increment', by: 2 })\n",[18,1238,1239,1261,1269,1273,1297],{"__ignoreMap":49},[53,1240,1241,1243,1246,1248,1251,1253,1255,1258],{"class":55,"line":56},[53,1242,60],{"class":59},[53,1244,1245],{"class":67}," renders",[53,1247,407],{"class":59},[53,1249,1250],{"class":83}," useRef",[53,1252,87],{"class":63},[53,1254,90],{"class":67},[53,1256,1257],{"class":63},")        ",[53,1259,1260],{"class":99},"\u002F\u002F bookkeeping, no re-render\n",[53,1262,1263,1266],{"class":55,"line":96},[53,1264,1265],{"class":63},"renders.current",[53,1267,1268],{"class":59},"++\n",[53,1270,1271],{"class":55,"line":194},[53,1272,318],{"emptyLinePlaceholder":317},[53,1274,1275,1277,1279,1282,1284,1287,1289,1291,1294],{"class":55,"line":215},[53,1276,60],{"class":59},[53,1278,64],{"class":63},[53,1280,1281],{"class":67},"state",[53,1283,71],{"class":63},[53,1285,1286],{"class":67},"dispatch",[53,1288,77],{"class":63},[53,1290,80],{"class":59},[53,1292,1293],{"class":83}," useReducer",[53,1295,1296],{"class":63},"(reducer, initial)\n",[53,1298,1299,1301,1304,1307,1310,1312],{"class":55,"line":227},[53,1300,1286],{"class":83},[53,1302,1303],{"class":63},"({ type: ",[53,1305,1306],{"class":757},"'increment'",[53,1308,1309],{"class":63},", by: ",[53,1311,543],{"class":67},[53,1313,998],{"class":63},[15,1315,1316],{},"A quick heuristic: if the UI depends on it, it's state; if you keep calling several\nsetters together, it's probably a reducer.",[10,1318,1320],{"id":1319},"sharing-and-resetting-state","Sharing and resetting state",[15,1322,1323,1324,1327],{},"When two components need the same data, ",[35,1325,1326],{},"lift state up"," to their closest common\nparent and pass it down as a prop plus a setter callback. The parent becomes the\nsingle source of truth.",[44,1329,1331],{"className":46,"code":1330,"language":48,"meta":49,"style":49},"function Parent() {\n  const [value, setValue] = useState('')\n  return (\n    \u003C>\n      \u003CInput value={value} onChange={setValue} \u002F>\n      \u003CPreview value={value} \u002F>\n    \u003C\u002F>\n  )\n}\n",[18,1332,1333,1342,1368,1376,1381,1405,1419,1424,1429],{"__ignoreMap":49},[53,1334,1335,1337,1340],{"class":55,"line":56},[53,1336,171],{"class":59},[53,1338,1339],{"class":83}," Parent",[53,1341,177],{"class":63},[53,1343,1344,1347,1349,1351,1353,1356,1358,1360,1362,1364,1366],{"class":55,"line":96},[53,1345,1346],{"class":59},"  const",[53,1348,64],{"class":63},[53,1350,136],{"class":67},[53,1352,71],{"class":63},[53,1354,1355],{"class":67},"setValue",[53,1357,77],{"class":63},[53,1359,80],{"class":59},[53,1361,84],{"class":83},[53,1363,87],{"class":63},[53,1365,990],{"class":757},[53,1367,93],{"class":63},[53,1369,1370,1373],{"class":55,"line":194},[53,1371,1372],{"class":59},"  return",[53,1374,1375],{"class":63}," (\n",[53,1377,1378],{"class":55,"line":215},[53,1379,1380],{"class":63},"    \u003C>\n",[53,1382,1383,1386,1389,1392,1394,1397,1400,1402],{"class":55,"line":227},[53,1384,1385],{"class":63},"      \u003C",[53,1387,1388],{"class":67},"Input",[53,1390,1391],{"class":83}," value",[53,1393,80],{"class":59},[53,1395,1396],{"class":63},"{value} ",[53,1398,1399],{"class":83},"onChange",[53,1401,80],{"class":59},[53,1403,1404],{"class":63},"{setValue} \u002F>\n",[53,1406,1407,1409,1412,1414,1416],{"class":55,"line":321},[53,1408,1385],{"class":63},[53,1410,1411],{"class":67},"Preview",[53,1413,1391],{"class":83},[53,1415,80],{"class":59},[53,1417,1418],{"class":63},"{value} \u002F>\n",[53,1420,1421],{"class":55,"line":327},[53,1422,1423],{"class":63},"    \u003C\u002F>\n",[53,1425,1426],{"class":55,"line":350},[53,1427,1428],{"class":63},"  )\n",[53,1430,1431],{"class":55,"line":369},[53,1432,230],{"class":63},[15,1434,1435,1436,1439,1440,1443],{},"To ",[35,1437,1438],{},"reset"," a component's state to its initial values, the cleanest trick is to\nchange its ",[18,1441,1442],{},"key",". React treats a new key as a brand-new component, throwing away the\nold state and mounting fresh — no manual reset code:",[44,1445,1447],{"className":46,"code":1446,"language":48,"meta":49,"style":49},"\u003CProfile key={userId} userId={userId} \u002F>\n",[18,1448,1449],{"__ignoreMap":49},[53,1450,1451,1454,1457,1460,1462,1465,1468,1470],{"class":55,"line":56},[53,1452,1453],{"class":63},"\u003C",[53,1455,1456],{"class":67},"Profile",[53,1458,1459],{"class":83}," key",[53,1461,80],{"class":59},[53,1463,1464],{"class":63},"{userId} ",[53,1466,1467],{"class":83},"userId",[53,1469,80],{"class":59},[53,1471,1472],{"class":63},"{userId} \u002F>\n",[15,1474,1475,1476,1479,1480,1482],{},"This also explains a classic gotcha: initializing state from a prop captures that\nprop ",[35,1477,1478],{},"only on the first render",", so later changes to the prop don't update the\nstate. If you need a reset when the prop changes, use ",[18,1481,1442],{}," rather than copying the\nprop into state.",[10,1484,1486],{"id":1485},"controlled-inputs","Controlled inputs",[15,1488,1489],{},"A controlled input reads its value from state and updates state on every keystroke,\nmaking React the single source of truth.",[44,1491,1493],{"className":46,"code":1492,"language":48,"meta":49,"style":49},"const [text, setText] = useState('')\n\u003Cinput value={text} onChange={e => setText(e.target.value)} \u002F>\n",[18,1494,1495,1521],{"__ignoreMap":49},[53,1496,1497,1499,1501,1504,1506,1509,1511,1513,1515,1517,1519],{"class":55,"line":56},[53,1498,60],{"class":59},[53,1500,64],{"class":63},[53,1502,1503],{"class":67},"text",[53,1505,71],{"class":63},[53,1507,1508],{"class":67},"setText",[53,1510,77],{"class":63},[53,1512,80],{"class":59},[53,1514,84],{"class":83},[53,1516,87],{"class":63},[53,1518,990],{"class":757},[53,1520,93],{"class":63},[53,1522,1523,1525,1529,1531,1533,1536,1538,1540,1543,1546,1548,1551],{"class":55,"line":96},[53,1524,1453],{"class":63},[53,1526,1528],{"class":1527},"s9eBZ","input",[53,1530,1391],{"class":83},[53,1532,80],{"class":59},[53,1534,1535],{"class":63},"{text} ",[53,1537,1399],{"class":83},[53,1539,80],{"class":59},[53,1541,1542],{"class":63},"{",[53,1544,1545],{"class":334},"e",[53,1547,338],{"class":59},[53,1549,1550],{"class":83}," setText",[53,1552,1553],{"class":63},"(e.target.value)} \u002F>\n",[15,1555,1556,1557,1559,1560,1562,1563,1566,1567,1570,1571,1574,1575,1578,1579,125,1582,1584],{},"Setting ",[18,1558,136],{}," without ",[18,1561,1399],{}," makes the field read-only (React warns). The\nalternative is an ",[35,1564,1565],{},"uncontrolled"," input, where the DOM holds the value and you read\nit via a ",[18,1568,1569],{},"ref"," when needed (",[18,1572,1573],{},"defaultValue"," for the initial value) — handy for simple\nforms and file inputs. For number inputs, remember the value is always a ",[35,1576,1577],{},"string",";\nkeep the raw string in state and parse where you use it, so a half-typed ",[18,1580,1581],{},"-",[18,1583,248],{},"\ndoesn't fight the user.",[10,1586,1588],{"id":1587},"rules-and-gotchas","Rules and gotchas",[1201,1590,1591,1600,1618,1624],{},[1204,1592,1593,1596,1597,1599],{},[35,1594,1595],{},"Call hooks at the top level."," React identifies each hook by call order, so never\nput ",[18,1598,20],{}," inside a condition, loop, or after an early return. Call it\nunconditionally and branch on the value.",[1204,1601,1602,1605,1606,1609,1610,1613,1614,1617],{},[35,1603,1604],{},"Same value bails out."," Setting state to a value that is ",[18,1607,1608],{},"Object.is","-equal to the\ncurrent one makes React skip the committed re-render. But this is ",[139,1611,1612],{},"reference","\nequality, so ",[18,1615,1616],{},"setItems([])"," with a fresh empty array still re-renders.",[1204,1619,1620,1623],{},[35,1621,1622],{},"Don't set state unconditionally during render"," — it causes an infinite loop. The\nsame applies to setting state in an effect whose dependency changes as a result.",[1204,1625,1626,1629,1630,1633,1634,1636],{},[35,1627,1628],{},"There's no setter callback."," The class ",[18,1631,1632],{},"this.setState(value, callback)"," second\nargument doesn't exist. To run code after an update commits, use a ",[18,1635,124],{}," that\ndepends on the value.",[44,1638,1640],{"className":46,"code":1639,"language":48,"meta":49,"style":49},"useEffect(() => {\n  analytics.track(count)   \u002F\u002F runs after the render caused by count changing\n}, [count])\n",[18,1641,1642,1653,1666],{"__ignoreMap":49},[53,1643,1644,1646,1648,1650],{"class":55,"line":56},[53,1645,124],{"class":83},[53,1647,665],{"class":63},[53,1649,413],{"class":59},[53,1651,1652],{"class":63}," {\n",[53,1654,1655,1658,1661,1663],{"class":55,"line":96},[53,1656,1657],{"class":63},"  analytics.",[53,1659,1660],{"class":83},"track",[53,1662,188],{"class":63},[53,1664,1665],{"class":99},"\u002F\u002F runs after the render caused by count changing\n",[53,1667,1668],{"class":55,"line":194},[53,1669,1670],{"class":63},"}, [count])\n",[10,1672,1674],{"id":1673},"recap","Recap",[15,1676,1677,1679,1680,1682,1683,1686,1687,1690,1691,1694,1695,1698,1699,794,1701,794,1703,1705,1706,1708,1709,1711],{},[18,1678,20],{}," gives you a value and a setter, but the depth is in the model behind them.\nState is a per-render ",[35,1681,236],{}," captured by closures, which is why values look\n\"one render behind\" and why stale closures happen. Updates are ",[35,1684,1685],{},"asynchronous and\nbatched",", so use the ",[35,1688,1689],{},"functional form"," whenever the next value depends on the\nprevious one. Treat objects and arrays as ",[35,1692,1693],{},"immutable",", producing new references so\nReact notices the change. Keep your state ",[35,1696,1697],{},"minimal"," — derive what you can, reach\nfor ",[18,1700,1218],{},[18,1702,958],{},[18,1704,1150],{}," when they fit better, lift state up to share it,\nand change the ",[18,1707,1442],{}," to reset it. Master those ideas and every ",[18,1710,20],{}," question,\nfrom lazy initialization to batching puzzles, becomes straightforward.",[1713,1714,1715],"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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-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 .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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":49,"searchDepth":96,"depth":96,"links":1717},[1718,1719,1720,1721,1725,1726,1727,1728,1729,1730,1731,1732],{"id":12,"depth":96,"text":13},{"id":27,"depth":96,"text":28},{"id":145,"depth":96,"text":146},{"id":254,"depth":96,"text":255,"children":1722},[1723,1724],{"id":259,"depth":194,"text":260},{"id":500,"depth":194,"text":501},{"id":587,"depth":96,"text":588},{"id":700,"depth":96,"text":701},{"id":1034,"depth":96,"text":1035},{"id":1198,"depth":96,"text":1199},{"id":1319,"depth":96,"text":1320},{"id":1485,"depth":96,"text":1486},{"id":1587,"depth":96,"text":1588},{"id":1673,"depth":96,"text":1674},"React useState interview questions — state updates, batching, functional updates, lazy initialization and why state seems one render behind.","easy","md","React","react",{},"\u002Fblog\u002Freact-usestate-hook-complete-guide","\u002Freact\u002Fhooks\u002Fusestate",{"title":5,"description":1733},"blog\u002Freact-usestate-hook-complete-guide","Hooks","hooks","2026-06-17","5oyffU9INlcBZCy3_hJh182-V1_WMMcMy7GuGvC8lEo",1781808673080]