[{"data":1,"prerenderedAt":1598},["ShallowReactive",2],{"blog-\u002Fblog\u002Freact-useref-hook-guide":3},{"id":4,"title":5,"body":6,"description":1584,"difficulty":1585,"extension":1586,"framework":1587,"frameworkSlug":1588,"meta":1589,"navigation":113,"order":216,"path":1590,"qaPath":1591,"seo":1592,"stem":1593,"subtopic":28,"topic":1594,"topicSlug":1595,"updated":1596,"__hash__":1597},"blog\u002Fblog\u002Freact-useref-hook-guide.md","React useRef Hook — Complete Interview Guide with Examples",{"type":7,"value":8,"toc":1571},"minimark",[9,14,23,37,41,62,130,137,141,147,263,279,283,351,420,423,427,433,438,538,543,610,619,623,630,636,758,765,769,783,901,904,908,917,1129,1140,1144,1158,1421,1424,1428,1445,1507,1510,1514,1567],[10,11,13],"h2",{"id":12},"what-the-react-useref-hook-does-and-why-interviewers-love-it","What the React useRef hook does — and why interviewers love it",[15,16,17,18,22],"p",{},"The ",[19,20,21],"strong",{},"React useRef hook"," is one of the most misunderstood pieces of the hooks API. On\nthe surface it's just a way to grab a DOM node. Beneath that, it's a general-purpose\nmutable container that survives renders without causing them — and understanding that\ndistinction separates \"I know hooks\" from \"I understand React's rendering model.\"",[15,24,25,29,30,33,34,36],{},[26,27,28],"code",{},"useRef"," comes up in almost every senior React interview because it sits at the\nintersection of React's declarative model and the imperative browser APIs that lie\noutside it. Questions range from \"how do you focus an input programmatically?\" to \"what\nis ",[26,31,32],{},"forwardRef"," and when would you use it?\" to \"how do you avoid stale closures in a\nlong-lived effect?\" All of them are ",[26,35,28],{}," questions in disguise.",[10,38,40],{"id":39},"the-shape-of-useref","The shape of useRef",[15,42,43,46,47,50,51,54,55,57,58,61],{},[26,44,45],{},"useRef(initialValue)"," returns a plain object with a single property: ",[26,48,49],{},".current",". That\nobject is created once and ",[19,52,53],{},"persists for the lifetime of the component",". Crucially,\nmutating ",[26,56,49],{}," does ",[19,59,60],{},"not"," trigger a re-render.",[63,64,69],"pre",{"className":65,"code":66,"language":67,"meta":68,"style":68},"language-jsx shiki shiki-themes github-light github-dark","const ref = useRef(null)\n\u002F\u002F ref.current === null until something sets it\n\nref.current = 42  \u002F\u002F mutation — no re-render\n","jsx","",[26,70,71,101,108,115],{"__ignoreMap":68},[72,73,76,80,84,87,91,95,98],"span",{"class":74,"line":75},"line",1,[72,77,79],{"class":78},"szBVR","const",[72,81,83],{"class":82},"sj4cs"," ref",[72,85,86],{"class":78}," =",[72,88,90],{"class":89},"sScJk"," useRef",[72,92,94],{"class":93},"sVt8B","(",[72,96,97],{"class":82},"null",[72,99,100],{"class":93},")\n",[72,102,104],{"class":74,"line":103},2,[72,105,107],{"class":106},"sJ8bj","\u002F\u002F ref.current === null until something sets it\n",[72,109,111],{"class":74,"line":110},3,[72,112,114],{"emptyLinePlaceholder":113},true,"\n",[72,116,118,121,124,127],{"class":74,"line":117},4,[72,119,120],{"class":93},"ref.current ",[72,122,123],{"class":78},"=",[72,125,126],{"class":82}," 42",[72,128,129],{"class":106},"  \u002F\u002F mutation — no re-render\n",[15,131,132,133,136],{},"Compare this with ",[26,134,135],{},"useState",": state changes cause renders; ref mutations don't. This is\nthe central difference. If the value needs to be visible on screen, it belongs in state.\nIf it's invisible bookkeeping, a ref is the right tool.",[10,138,140],{"id":139},"accessing-dom-nodes","Accessing DOM nodes",[15,142,143,144,146],{},"The most common ",[26,145,28],{}," use case: grab a DOM node to do something the React model can't\nexpress declaratively — focus, scroll, measure.",[63,148,150],{"className":65,"code":149,"language":67,"meta":68,"style":68},"function SearchBar() {\n  const inputRef = useRef(null)\n\n  useEffect(() => {\n    inputRef.current.focus() \u002F\u002F focus after mount\n  }, [])\n\n  return \u003Cinput ref={inputRef} placeholder=\"Search...\" \u002F>\n}\n",[26,151,152,163,181,185,199,214,220,225,257],{"__ignoreMap":68},[72,153,154,157,160],{"class":74,"line":75},[72,155,156],{"class":78},"function",[72,158,159],{"class":89}," SearchBar",[72,161,162],{"class":93},"() {\n",[72,164,165,168,171,173,175,177,179],{"class":74,"line":103},[72,166,167],{"class":78},"  const",[72,169,170],{"class":82}," inputRef",[72,172,86],{"class":78},[72,174,90],{"class":89},[72,176,94],{"class":93},[72,178,97],{"class":82},[72,180,100],{"class":93},[72,182,183],{"class":74,"line":110},[72,184,114],{"emptyLinePlaceholder":113},[72,186,187,190,193,196],{"class":74,"line":117},[72,188,189],{"class":89},"  useEffect",[72,191,192],{"class":93},"(() ",[72,194,195],{"class":78},"=>",[72,197,198],{"class":93}," {\n",[72,200,202,205,208,211],{"class":74,"line":201},5,[72,203,204],{"class":93},"    inputRef.current.",[72,206,207],{"class":89},"focus",[72,209,210],{"class":93},"() ",[72,212,213],{"class":106},"\u002F\u002F focus after mount\n",[72,215,217],{"class":74,"line":216},6,[72,218,219],{"class":93},"  }, [])\n",[72,221,223],{"class":74,"line":222},7,[72,224,114],{"emptyLinePlaceholder":113},[72,226,228,231,234,238,240,242,245,248,250,254],{"class":74,"line":227},8,[72,229,230],{"class":78},"  return",[72,232,233],{"class":93}," \u003C",[72,235,237],{"class":236},"s9eBZ","input",[72,239,83],{"class":89},[72,241,123],{"class":78},[72,243,244],{"class":93},"{inputRef} ",[72,246,247],{"class":89},"placeholder",[72,249,123],{"class":78},[72,251,253],{"class":252},"sZZnC","\"Search...\"",[72,255,256],{"class":93}," \u002F>\n",[72,258,260],{"class":74,"line":259},9,[72,261,262],{"class":93},"}\n",[15,264,265,266,269,270,272,273,275,276,278],{},"React sets ",[26,267,268],{},"ref.current"," to the DOM node when the component mounts and resets it to\n",[26,271,97],{}," when the component unmounts. Read DOM refs inside effects or event handlers —\n",[26,274,268],{}," is ",[26,277,97],{}," during the first render pass.",[10,280,282],{"id":281},"useref-vs-usestate-when-to-reach-for-which","useRef vs useState — when to reach for which",[284,285,286,303],"table",{},[287,288,289],"thead",{},[290,291,292,295,299],"tr",{},[293,294],"th",{},[293,296,297],{},[26,298,28],{},[293,300,301],{},[26,302,135],{},[304,305,306,318,327,336],"tbody",{},[290,307,308,312,315],{},[309,310,311],"td",{},"Triggers re-render",[309,313,314],{},"No",[309,316,317],{},"Yes",[290,319,320,323,325],{},[309,321,322],{},"Persists across renders",[309,324,317],{},[309,326,317],{},[290,328,329,332,334],{},[309,330,331],{},"Use for UI output",[309,333,314],{},[309,335,317],{},[290,337,338,341,348],{},[309,339,340],{},"Mutate directly",[309,342,343,344,347],{},"Yes (",[26,345,346],{},"ref.current = x",")",[309,349,350],{},"No (use setter)",[63,352,354],{"className":65,"code":353,"language":67,"meta":68,"style":68},"const [count, setCount] = useState(0) \u002F\u002F displayed on screen -> needs state\nconst renderCount = useRef(0)          \u002F\u002F bookkeeping -> ref is enough\nrenderCount.current++\n",[26,355,356,391,412],{"__ignoreMap":68},[72,357,358,360,363,366,369,372,375,377,380,382,385,388],{"class":74,"line":75},[72,359,79],{"class":78},[72,361,362],{"class":93}," [",[72,364,365],{"class":82},"count",[72,367,368],{"class":93},", ",[72,370,371],{"class":82},"setCount",[72,373,374],{"class":93},"] ",[72,376,123],{"class":78},[72,378,379],{"class":89}," useState",[72,381,94],{"class":93},[72,383,384],{"class":82},"0",[72,386,387],{"class":93},") ",[72,389,390],{"class":106},"\u002F\u002F displayed on screen -> needs state\n",[72,392,393,395,398,400,402,404,406,409],{"class":74,"line":103},[72,394,79],{"class":78},[72,396,397],{"class":82}," renderCount",[72,399,86],{"class":78},[72,401,90],{"class":89},[72,403,94],{"class":93},[72,405,384],{"class":82},[72,407,408],{"class":93},")          ",[72,410,411],{"class":106},"\u002F\u002F bookkeeping -> ref is enough\n",[72,413,414,417],{"class":74,"line":110},[72,415,416],{"class":93},"renderCount.current",[72,418,419],{"class":78},"++\n",[15,421,422],{},"A common mistake: using a ref for a value that's displayed in JSX. The UI won't update\nbecause React never knows the ref changed. If the view depends on the value, it must be\nstate.",[10,424,426],{"id":425},"storing-mutable-values-timers-ids-and-previous-values","Storing mutable values: timers, IDs, and previous values",[15,428,429,430,432],{},"Any mutable \"instance variable\" that needs to survive renders without causing them is a\n",[26,431,28],{}," candidate.",[15,434,435],{},[19,436,437],{},"Timer ID:",[63,439,441],{"className":65,"code":440,"language":67,"meta":68,"style":68},"const timerId = useRef(null)\n\nuseEffect(() => {\n  timerId.current = setInterval(tick, 1000)\n  return () => clearInterval(timerId.current)\n}, [])\n\nfunction stop() {\n  clearInterval(timerId.current)\n}\n",[26,442,443,460,464,475,493,508,513,517,526,533],{"__ignoreMap":68},[72,444,445,447,450,452,454,456,458],{"class":74,"line":75},[72,446,79],{"class":78},[72,448,449],{"class":82}," timerId",[72,451,86],{"class":78},[72,453,90],{"class":89},[72,455,94],{"class":93},[72,457,97],{"class":82},[72,459,100],{"class":93},[72,461,462],{"class":74,"line":103},[72,463,114],{"emptyLinePlaceholder":113},[72,465,466,469,471,473],{"class":74,"line":110},[72,467,468],{"class":89},"useEffect",[72,470,192],{"class":93},[72,472,195],{"class":78},[72,474,198],{"class":93},[72,476,477,480,482,485,488,491],{"class":74,"line":117},[72,478,479],{"class":93},"  timerId.current ",[72,481,123],{"class":78},[72,483,484],{"class":89}," setInterval",[72,486,487],{"class":93},"(tick, ",[72,489,490],{"class":82},"1000",[72,492,100],{"class":93},[72,494,495,497,500,502,505],{"class":74,"line":201},[72,496,230],{"class":78},[72,498,499],{"class":93}," () ",[72,501,195],{"class":78},[72,503,504],{"class":89}," clearInterval",[72,506,507],{"class":93},"(timerId.current)\n",[72,509,510],{"class":74,"line":216},[72,511,512],{"class":93},"}, [])\n",[72,514,515],{"class":74,"line":222},[72,516,114],{"emptyLinePlaceholder":113},[72,518,519,521,524],{"class":74,"line":227},[72,520,156],{"class":78},[72,522,523],{"class":89}," stop",[72,525,162],{"class":93},[72,527,528,531],{"class":74,"line":259},[72,529,530],{"class":89},"  clearInterval",[72,532,507],{"class":93},[72,534,536],{"class":74,"line":535},10,[72,537,262],{"class":93},[15,539,540],{},[19,541,542],{},"Previous value:",[63,544,546],{"className":65,"code":545,"language":67,"meta":68,"style":68},"function usePrevious(value) {\n  const ref = useRef()\n  useEffect(() => { ref.current = value }) \u002F\u002F updates after render\n  return ref.current                        \u002F\u002F returns last render's value\n}\n",[26,547,548,564,577,596,606],{"__ignoreMap":68},[72,549,550,552,555,557,561],{"class":74,"line":75},[72,551,156],{"class":78},[72,553,554],{"class":89}," usePrevious",[72,556,94],{"class":93},[72,558,560],{"class":559},"s4XuR","value",[72,562,563],{"class":93},") {\n",[72,565,566,568,570,572,574],{"class":74,"line":103},[72,567,167],{"class":78},[72,569,83],{"class":82},[72,571,86],{"class":78},[72,573,90],{"class":89},[72,575,576],{"class":93},"()\n",[72,578,579,581,583,585,588,590,593],{"class":74,"line":110},[72,580,189],{"class":89},[72,582,192],{"class":93},[72,584,195],{"class":78},[72,586,587],{"class":93}," { ref.current ",[72,589,123],{"class":78},[72,591,592],{"class":93}," value }) ",[72,594,595],{"class":106},"\u002F\u002F updates after render\n",[72,597,598,600,603],{"class":74,"line":117},[72,599,230],{"class":78},[72,601,602],{"class":93}," ref.current                        ",[72,604,605],{"class":106},"\u002F\u002F returns last render's value\n",[72,607,608],{"class":74,"line":201},[72,609,262],{"class":93},[15,611,612,613,615,616,618],{},"The previous-value pattern works because ",[26,614,468],{}," (with no deps) runs after every\nrender — so ",[26,617,268],{}," during the current render still holds the value from the\nprevious one.",[10,620,622],{"id":621},"the-latest-value-ref-pattern-for-long-lived-effects","The \"latest value\" ref pattern for long-lived effects",[15,624,625,626,629],{},"A long-lived effect (WebSocket, setInterval, event listener) captures props and state\nfrom the render it was created in. If those values change later, the effect has a stale\nsnapshot — a classic ",[19,627,628],{},"stale closure"," bug.",[15,631,632,633,635],{},"The fix: mirror the latest value in a ref that's updated every render. The long-lived\ncallback reads ",[26,634,268],{}," instead of the closed-over snapshot.",[63,637,639],{"className":65,"code":638,"language":67,"meta":68,"style":68},"function useInterval(callback, delay) {\n  const callbackRef = useRef(callback)\n  useEffect(() => { callbackRef.current = callback }) \u002F\u002F always current\n\n  useEffect(() => {\n    const id = setInterval(() => callbackRef.current(), delay)\n    return () => clearInterval(id)\n  }, [delay]) \u002F\u002F interval created once; callback is always fresh via ref\n}\n",[26,640,641,660,674,693,697,707,732,746,754],{"__ignoreMap":68},[72,642,643,645,648,650,653,655,658],{"class":74,"line":75},[72,644,156],{"class":78},[72,646,647],{"class":89}," useInterval",[72,649,94],{"class":93},[72,651,652],{"class":559},"callback",[72,654,368],{"class":93},[72,656,657],{"class":559},"delay",[72,659,563],{"class":93},[72,661,662,664,667,669,671],{"class":74,"line":103},[72,663,167],{"class":78},[72,665,666],{"class":82}," callbackRef",[72,668,86],{"class":78},[72,670,90],{"class":89},[72,672,673],{"class":93},"(callback)\n",[72,675,676,678,680,682,685,687,690],{"class":74,"line":110},[72,677,189],{"class":89},[72,679,192],{"class":93},[72,681,195],{"class":78},[72,683,684],{"class":93}," { callbackRef.current ",[72,686,123],{"class":78},[72,688,689],{"class":93}," callback }) ",[72,691,692],{"class":106},"\u002F\u002F always current\n",[72,694,695],{"class":74,"line":117},[72,696,114],{"emptyLinePlaceholder":113},[72,698,699,701,703,705],{"class":74,"line":201},[72,700,189],{"class":89},[72,702,192],{"class":93},[72,704,195],{"class":78},[72,706,198],{"class":93},[72,708,709,712,715,717,719,721,723,726,729],{"class":74,"line":216},[72,710,711],{"class":78},"    const",[72,713,714],{"class":82}," id",[72,716,86],{"class":78},[72,718,484],{"class":89},[72,720,192],{"class":93},[72,722,195],{"class":78},[72,724,725],{"class":93}," callbackRef.",[72,727,728],{"class":89},"current",[72,730,731],{"class":93},"(), delay)\n",[72,733,734,737,739,741,743],{"class":74,"line":222},[72,735,736],{"class":78},"    return",[72,738,499],{"class":93},[72,740,195],{"class":78},[72,742,504],{"class":89},[72,744,745],{"class":93},"(id)\n",[72,747,748,751],{"class":74,"line":227},[72,749,750],{"class":93},"  }, [delay]) ",[72,752,753],{"class":106},"\u002F\u002F interval created once; callback is always fresh via ref\n",[72,755,756],{"class":74,"line":259},[72,757,262],{"class":93},[15,759,760,761,764],{},"This is the foundation of the ",[26,762,763],{},"useEventCallback"," pattern used in many hook libraries.\nIt avoids tearing down and recreating expensive subscriptions on every callback change.",[10,766,768],{"id":767},"callback-refs-for-dynamic-or-conditional-elements","Callback refs — for dynamic or conditional elements",[15,770,771,772,775,776,779,780,782],{},"A regular ref object doesn't notify you when it gets attached to a node. A ",[19,773,774],{},"callback\nref"," (a function passed to the ",[26,777,778],{},"ref"," attribute) fires with the DOM node on mount and\n",[26,781,97],{}," on unmount — exactly when you need it.",[63,784,786],{"className":65,"code":785,"language":67,"meta":68,"style":68},"function MeasureDiv() {\n  const [height, setHeight] = useState(null)\n\n  const measuredRef = useCallback(node => {\n    if (node) setHeight(node.getBoundingClientRect().height)\n  }, [])\n\n  return \u003Cdiv ref={measuredRef}>content — {height}px tall\u003C\u002Fdiv>\n}\n",[26,787,788,797,823,827,849,868,872,876,897],{"__ignoreMap":68},[72,789,790,792,795],{"class":74,"line":75},[72,791,156],{"class":78},[72,793,794],{"class":89}," MeasureDiv",[72,796,162],{"class":93},[72,798,799,801,803,806,808,811,813,815,817,819,821],{"class":74,"line":103},[72,800,167],{"class":78},[72,802,362],{"class":93},[72,804,805],{"class":82},"height",[72,807,368],{"class":93},[72,809,810],{"class":82},"setHeight",[72,812,374],{"class":93},[72,814,123],{"class":78},[72,816,379],{"class":89},[72,818,94],{"class":93},[72,820,97],{"class":82},[72,822,100],{"class":93},[72,824,825],{"class":74,"line":110},[72,826,114],{"emptyLinePlaceholder":113},[72,828,829,831,834,836,839,841,844,847],{"class":74,"line":117},[72,830,167],{"class":78},[72,832,833],{"class":82}," measuredRef",[72,835,86],{"class":78},[72,837,838],{"class":89}," useCallback",[72,840,94],{"class":93},[72,842,843],{"class":559},"node",[72,845,846],{"class":78}," =>",[72,848,198],{"class":93},[72,850,851,854,857,859,862,865],{"class":74,"line":201},[72,852,853],{"class":78},"    if",[72,855,856],{"class":93}," (node) ",[72,858,810],{"class":89},[72,860,861],{"class":93},"(node.",[72,863,864],{"class":89},"getBoundingClientRect",[72,866,867],{"class":93},"().height)\n",[72,869,870],{"class":74,"line":216},[72,871,219],{"class":93},[72,873,874],{"class":74,"line":222},[72,875,114],{"emptyLinePlaceholder":113},[72,877,878,880,882,885,887,889,892,894],{"class":74,"line":227},[72,879,230],{"class":78},[72,881,233],{"class":93},[72,883,884],{"class":236},"div",[72,886,83],{"class":89},[72,888,123],{"class":78},[72,890,891],{"class":93},"{measuredRef}>content — {height}px tall\u003C\u002F",[72,893,884],{"class":236},[72,895,896],{"class":93},">\n",[72,898,899],{"class":74,"line":259},[72,900,262],{"class":93},[15,902,903],{},"Use callback refs when: the element is conditionally rendered, the number of elements\nchanges, or you need to run a side effect the instant the node is available.",[10,905,907],{"id":906},"forwardref-exposing-a-childs-dom-node-to-the-parent","forwardRef — exposing a child's DOM node to the parent",[15,909,910,911,913,914,916],{},"Function components can't receive a ",[26,912,778],{}," prop by default (there's no instance for it to\npoint to). Wrap the component in ",[26,915,32],{}," to let a parent reach a DOM node inside.",[63,918,920],{"className":65,"code":919,"language":67,"meta":68,"style":68},"const TextInput = forwardRef(function TextInput({ label }, ref) {\n  return (\n    \u003Clabel>\n      {label}\n      \u003Cinput ref={ref} type=\"text\" \u002F>\n    \u003C\u002Flabel>\n  )\n})\n\nfunction Form() {\n  const inputRef = useRef(null)\n  return (\n    \u003C>\n      \u003CTextInput label=\"Name\" ref={inputRef} \u002F>\n      \u003Cbutton onClick={() => inputRef.current.focus()}>Focus\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n",[26,921,922,953,960,969,974,998,1007,1012,1017,1021,1030,1047,1054,1060,1083,1113,1119,1124],{"__ignoreMap":68},[72,923,924,926,929,931,934,936,938,940,943,946,949,951],{"class":74,"line":75},[72,925,79],{"class":78},[72,927,928],{"class":82}," TextInput",[72,930,86],{"class":78},[72,932,933],{"class":89}," forwardRef",[72,935,94],{"class":93},[72,937,156],{"class":78},[72,939,928],{"class":89},[72,941,942],{"class":93},"({ ",[72,944,945],{"class":559},"label",[72,947,948],{"class":93}," }, ",[72,950,778],{"class":559},[72,952,563],{"class":93},[72,954,955,957],{"class":74,"line":103},[72,956,230],{"class":78},[72,958,959],{"class":93}," (\n",[72,961,962,965,967],{"class":74,"line":110},[72,963,964],{"class":93},"    \u003C",[72,966,945],{"class":236},[72,968,896],{"class":93},[72,970,971],{"class":74,"line":117},[72,972,973],{"class":93},"      {label}\n",[72,975,976,979,981,983,985,988,991,993,996],{"class":74,"line":201},[72,977,978],{"class":93},"      \u003C",[72,980,237],{"class":236},[72,982,83],{"class":89},[72,984,123],{"class":78},[72,986,987],{"class":93},"{ref} ",[72,989,990],{"class":89},"type",[72,992,123],{"class":78},[72,994,995],{"class":252},"\"text\"",[72,997,256],{"class":93},[72,999,1000,1003,1005],{"class":74,"line":216},[72,1001,1002],{"class":93},"    \u003C\u002F",[72,1004,945],{"class":236},[72,1006,896],{"class":93},[72,1008,1009],{"class":74,"line":222},[72,1010,1011],{"class":93},"  )\n",[72,1013,1014],{"class":74,"line":227},[72,1015,1016],{"class":93},"})\n",[72,1018,1019],{"class":74,"line":259},[72,1020,114],{"emptyLinePlaceholder":113},[72,1022,1023,1025,1028],{"class":74,"line":535},[72,1024,156],{"class":78},[72,1026,1027],{"class":89}," Form",[72,1029,162],{"class":93},[72,1031,1033,1035,1037,1039,1041,1043,1045],{"class":74,"line":1032},11,[72,1034,167],{"class":78},[72,1036,170],{"class":82},[72,1038,86],{"class":78},[72,1040,90],{"class":89},[72,1042,94],{"class":93},[72,1044,97],{"class":82},[72,1046,100],{"class":93},[72,1048,1050,1052],{"class":74,"line":1049},12,[72,1051,230],{"class":78},[72,1053,959],{"class":93},[72,1055,1057],{"class":74,"line":1056},13,[72,1058,1059],{"class":93},"    \u003C>\n",[72,1061,1063,1065,1068,1071,1073,1076,1078,1080],{"class":74,"line":1062},14,[72,1064,978],{"class":93},[72,1066,1067],{"class":82},"TextInput",[72,1069,1070],{"class":89}," label",[72,1072,123],{"class":78},[72,1074,1075],{"class":252},"\"Name\"",[72,1077,83],{"class":89},[72,1079,123],{"class":78},[72,1081,1082],{"class":93},"{inputRef} \u002F>\n",[72,1084,1086,1088,1091,1094,1096,1099,1101,1104,1106,1109,1111],{"class":74,"line":1085},15,[72,1087,978],{"class":93},[72,1089,1090],{"class":236},"button",[72,1092,1093],{"class":89}," onClick",[72,1095,123],{"class":78},[72,1097,1098],{"class":93},"{() ",[72,1100,195],{"class":78},[72,1102,1103],{"class":93}," inputRef.current.",[72,1105,207],{"class":89},[72,1107,1108],{"class":93},"()}>Focus\u003C\u002F",[72,1110,1090],{"class":236},[72,1112,896],{"class":93},[72,1114,1116],{"class":74,"line":1115},16,[72,1117,1118],{"class":93},"    \u003C\u002F>\n",[72,1120,1122],{"class":74,"line":1121},17,[72,1123,1011],{"class":93},[72,1125,1127],{"class":74,"line":1126},18,[72,1128,262],{"class":93},[15,1130,1131,1133,1134,1136,1137,1139],{},[26,1132,32],{}," is the correct API in React 18 and below. React 19 makes ",[26,1135,778],{}," a plain prop\non function components, eliminating the need for ",[26,1138,32],{},".",[10,1141,1143],{"id":1142},"useimperativehandle-controlling-the-exposed-api","useImperativeHandle — controlling the exposed API",[15,1145,1146,1147,1149,1150,1153,1154,1157],{},"By default, ",[26,1148,32],{}," exposes the raw DOM node. ",[26,1151,1152],{},"useImperativeHandle"," lets you expose\na ",[19,1155,1156],{},"custom API"," instead — useful for encapsulating behavior and hiding implementation\ndetails.",[63,1159,1161],{"className":65,"code":1160,"language":67,"meta":68,"style":68},"const Dialog = forwardRef(function Dialog(props, ref) {\n  const [open, setOpen] = useState(false)\n\n  useImperativeHandle(ref, () => ({\n    open:  () => setOpen(true),\n    close: () => setOpen(false),\n  }))\n\n  return open ? \u003Cdiv role=\"dialog\">{props.children}\u003C\u002Fdiv> : null\n})\n\nfunction App() {\n  const dialogRef = useRef(null)\n  return (\n    \u003C>\n      \u003Cbutton onClick={() => dialogRef.current.open()}>Open dialog\u003C\u002Fbutton>\n      \u003CDialog ref={dialogRef}>Hello\u003C\u002FDialog>\n    \u003C\u002F>\n  )\n}\n",[26,1162,1163,1191,1218,1222,1235,1256,1274,1279,1283,1319,1323,1327,1336,1353,1359,1363,1389,1407,1411,1416],{"__ignoreMap":68},[72,1164,1165,1167,1170,1172,1174,1176,1178,1180,1182,1185,1187,1189],{"class":74,"line":75},[72,1166,79],{"class":78},[72,1168,1169],{"class":82}," Dialog",[72,1171,86],{"class":78},[72,1173,933],{"class":89},[72,1175,94],{"class":93},[72,1177,156],{"class":78},[72,1179,1169],{"class":89},[72,1181,94],{"class":93},[72,1183,1184],{"class":559},"props",[72,1186,368],{"class":93},[72,1188,778],{"class":559},[72,1190,563],{"class":93},[72,1192,1193,1195,1197,1200,1202,1205,1207,1209,1211,1213,1216],{"class":74,"line":103},[72,1194,167],{"class":78},[72,1196,362],{"class":93},[72,1198,1199],{"class":82},"open",[72,1201,368],{"class":93},[72,1203,1204],{"class":82},"setOpen",[72,1206,374],{"class":93},[72,1208,123],{"class":78},[72,1210,379],{"class":89},[72,1212,94],{"class":93},[72,1214,1215],{"class":82},"false",[72,1217,100],{"class":93},[72,1219,1220],{"class":74,"line":110},[72,1221,114],{"emptyLinePlaceholder":113},[72,1223,1224,1227,1230,1232],{"class":74,"line":117},[72,1225,1226],{"class":89},"  useImperativeHandle",[72,1228,1229],{"class":93},"(ref, () ",[72,1231,195],{"class":78},[72,1233,1234],{"class":93}," ({\n",[72,1236,1237,1240,1243,1245,1248,1250,1253],{"class":74,"line":201},[72,1238,1239],{"class":89},"    open",[72,1241,1242],{"class":93},":  () ",[72,1244,195],{"class":78},[72,1246,1247],{"class":89}," setOpen",[72,1249,94],{"class":93},[72,1251,1252],{"class":82},"true",[72,1254,1255],{"class":93},"),\n",[72,1257,1258,1261,1264,1266,1268,1270,1272],{"class":74,"line":216},[72,1259,1260],{"class":89},"    close",[72,1262,1263],{"class":93},": () ",[72,1265,195],{"class":78},[72,1267,1247],{"class":89},[72,1269,94],{"class":93},[72,1271,1215],{"class":82},[72,1273,1255],{"class":93},[72,1275,1276],{"class":74,"line":222},[72,1277,1278],{"class":93},"  }))\n",[72,1280,1281],{"class":74,"line":227},[72,1282,114],{"emptyLinePlaceholder":113},[72,1284,1285,1287,1290,1293,1295,1297,1300,1302,1305,1308,1310,1313,1316],{"class":74,"line":259},[72,1286,230],{"class":78},[72,1288,1289],{"class":93}," open ",[72,1291,1292],{"class":78},"?",[72,1294,233],{"class":93},[72,1296,884],{"class":236},[72,1298,1299],{"class":89}," role",[72,1301,123],{"class":78},[72,1303,1304],{"class":252},"\"dialog\"",[72,1306,1307],{"class":93},">{props.children}\u003C\u002F",[72,1309,884],{"class":236},[72,1311,1312],{"class":93},"> ",[72,1314,1315],{"class":78},":",[72,1317,1318],{"class":82}," null\n",[72,1320,1321],{"class":74,"line":535},[72,1322,1016],{"class":93},[72,1324,1325],{"class":74,"line":1032},[72,1326,114],{"emptyLinePlaceholder":113},[72,1328,1329,1331,1334],{"class":74,"line":1049},[72,1330,156],{"class":78},[72,1332,1333],{"class":89}," App",[72,1335,162],{"class":93},[72,1337,1338,1340,1343,1345,1347,1349,1351],{"class":74,"line":1056},[72,1339,167],{"class":78},[72,1341,1342],{"class":82}," dialogRef",[72,1344,86],{"class":78},[72,1346,90],{"class":89},[72,1348,94],{"class":93},[72,1350,97],{"class":82},[72,1352,100],{"class":93},[72,1354,1355,1357],{"class":74,"line":1062},[72,1356,230],{"class":78},[72,1358,959],{"class":93},[72,1360,1361],{"class":74,"line":1085},[72,1362,1059],{"class":93},[72,1364,1365,1367,1369,1371,1373,1375,1377,1380,1382,1385,1387],{"class":74,"line":1115},[72,1366,978],{"class":93},[72,1368,1090],{"class":236},[72,1370,1093],{"class":89},[72,1372,123],{"class":78},[72,1374,1098],{"class":93},[72,1376,195],{"class":78},[72,1378,1379],{"class":93}," dialogRef.current.",[72,1381,1199],{"class":89},[72,1383,1384],{"class":93},"()}>Open dialog\u003C\u002F",[72,1386,1090],{"class":236},[72,1388,896],{"class":93},[72,1390,1391,1393,1396,1398,1400,1403,1405],{"class":74,"line":1121},[72,1392,978],{"class":93},[72,1394,1395],{"class":82},"Dialog",[72,1397,83],{"class":89},[72,1399,123],{"class":78},[72,1401,1402],{"class":93},"{dialogRef}>Hello\u003C\u002F",[72,1404,1395],{"class":82},[72,1406,896],{"class":93},[72,1408,1409],{"class":74,"line":1126},[72,1410,1118],{"class":93},[72,1412,1414],{"class":74,"line":1413},19,[72,1415,1011],{"class":93},[72,1417,1419],{"class":74,"line":1418},20,[72,1420,262],{"class":93},[15,1422,1423],{},"This is an escape hatch for legitimately imperative APIs (animations, focus sequences,\nmedia controls). Keep it narrow — expose methods, not state.",[10,1425,1427],{"id":1426},"per-instance-vs-module-level-variables","Per-instance vs. module-level variables",[15,1429,1430,1431,1434,1435,1438,1439,1442,1443,1139],{},"A module-level ",[26,1432,1433],{},"let"," is shared across ",[19,1436,1437],{},"all instances"," of the component. A ref is\n",[19,1440,1441],{},"per-instance"," — each mounted copy has its own ",[26,1444,49],{},[63,1446,1448],{"className":65,"code":1447,"language":67,"meta":68,"style":68},"let sharedTimer = null  \u002F\u002F shared! every Poller reads the same variable\n\nfunction Poller() {\n  const timerRef = useRef(null) \u002F\u002F isolated; each Poller owns its timer\n  \u002F\u002F ...\n}\n",[26,1449,1450,1465,1469,1478,1498,1503],{"__ignoreMap":68},[72,1451,1452,1454,1457,1459,1462],{"class":74,"line":75},[72,1453,1433],{"class":78},[72,1455,1456],{"class":93}," sharedTimer ",[72,1458,123],{"class":78},[72,1460,1461],{"class":82}," null",[72,1463,1464],{"class":106},"  \u002F\u002F shared! every Poller reads the same variable\n",[72,1466,1467],{"class":74,"line":103},[72,1468,114],{"emptyLinePlaceholder":113},[72,1470,1471,1473,1476],{"class":74,"line":110},[72,1472,156],{"class":78},[72,1474,1475],{"class":89}," Poller",[72,1477,162],{"class":93},[72,1479,1480,1482,1485,1487,1489,1491,1493,1495],{"class":74,"line":117},[72,1481,167],{"class":78},[72,1483,1484],{"class":82}," timerRef",[72,1486,86],{"class":78},[72,1488,90],{"class":89},[72,1490,94],{"class":93},[72,1492,97],{"class":82},[72,1494,387],{"class":93},[72,1496,1497],{"class":106},"\u002F\u002F isolated; each Poller owns its timer\n",[72,1499,1500],{"class":74,"line":201},[72,1501,1502],{"class":106},"  \u002F\u002F ...\n",[72,1504,1505],{"class":74,"line":216},[72,1506,262],{"class":93},[15,1508,1509],{},"Whenever you're tempted to reach for a module-level variable to persist something across\nrenders, ask: does it need to be per-instance? If yes, use a ref.",[10,1511,1513],{"id":1512},"common-interview-questions-at-a-glance","Common interview questions at a glance",[1515,1516,1517,1528,1534,1540,1546,1552,1561],"ul",{},[1518,1519,1520,1523,1524,1527],"li",{},[19,1521,1522],{},"What does useRef return?"," A mutable object ",[26,1525,1526],{},"{ current: initialValue }"," that\npersists for the component's lifetime. Mutations don't cause re-renders.",[1518,1529,1530,1533],{},[19,1531,1532],{},"How does useRef differ from useState?"," Mutations are invisible to React — no\nre-render. Use refs for invisible bookkeeping, state for things that appear in the UI.",[1518,1535,1536,1539],{},[19,1537,1538],{},"When is ref.current null?"," During the first render (before mount) and after unmount.\nDOM refs are only safe inside effects and event handlers.",[1518,1541,1542,1545],{},[19,1543,1544],{},"What is forwardRef?"," A wrapper that lets a parent pass a ref into a function\ncomponent so it can attach to an internal DOM node.",[1518,1547,1548,1551],{},[19,1549,1550],{},"What is useImperativeHandle?"," Customizes the value the parent's ref receives —\nexposing a method API instead of a raw DOM node.",[1518,1553,1554,1557,1558,1560],{},[19,1555,1556],{},"What is the callback ref pattern?"," Passing a function to ",[26,1559,778],{}," instead of a ref\nobject, so you get notified the moment a DOM node mounts or unmounts.",[1518,1562,1563,1566],{},[19,1564,1565],{},"What is the \"latest value\" ref pattern?"," Mirroring a prop\u002Fstate into a ref each\nrender so a long-lived callback always reads fresh data without stale closures.",[1568,1569,1570],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":68,"searchDepth":103,"depth":103,"links":1572},[1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583],{"id":12,"depth":103,"text":13},{"id":39,"depth":103,"text":40},{"id":139,"depth":103,"text":140},{"id":281,"depth":103,"text":282},{"id":425,"depth":103,"text":426},{"id":621,"depth":103,"text":622},{"id":767,"depth":103,"text":768},{"id":906,"depth":103,"text":907},{"id":1142,"depth":103,"text":1143},{"id":1426,"depth":103,"text":1427},{"id":1512,"depth":103,"text":1513},"Master the React useRef hook for interviews — DOM access, mutable instance variables, forwardRef, useImperativeHandle, callback refs, and when to use ref vs state.","medium","md","React","react",{},"\u002Fblog\u002Freact-useref-hook-guide","\u002Freact\u002Fhooks\u002Fuseref",{"title":5,"description":1584},"blog\u002Freact-useref-hook-guide","Hooks","hooks","2026-06-23","TqOrIPjgXr5oIZjHJOSrbCbHN_8U3HC1QbBhddBugmE",1782244083422]