{"id":155,"date":"2026-10-16T04:33:09","date_gmt":"2026-10-16T04:33:09","guid":{"rendered":"https:\/\/webdevpuneet.com\/blog\/?p=155"},"modified":"2026-10-16T04:34:50","modified_gmt":"2026-10-16T04:34:50","slug":"react-useref-explained-8-practical-uses-beyond-the-dom","status":"publish","type":"post","link":"https:\/\/webdevpuneet.com\/blog\/react-useref-explained-8-practical-uses-beyond-the-dom\/","title":{"rendered":"React useRef Explained: 8 Practical Uses Beyond the DOM"},"content":{"rendered":"<p><!--\n================================================================\n  POST TITLE (paste into the WordPress title field \u2014 it becomes the page H1)\n  React useRef Explained: 8 Practical Uses Beyond the DOM\n\n  SEO TITLE (55 chars, target 50\u201360, max 60 \u2014 \"Optimize SEO\" panel \u2192 SEO TITLE)\n  React useRef Explained: 8 Practical Uses Beyond the DOM\n\n  SEO DESCRIPTION (155 chars, target 150\u2013160, max 160 \u2014 \"Optimize SEO\" panel \u2192 SEO DESCRIPTION)\n  React useRef explained with 8 live demos: focus, values that don't re-render, previous values, stale closures, uncontrolled inputs, click-outside and more.\n\n  HOW TO PASTE: click the first line after this comment, press Ctrl+Shift+End, Ctrl+C.\n  In WordPress click \"Type \/ to choose a block\", then Ctrl+V.\n  Fallback: \u22ee menu \u2192 Code editor \u2192 paste.\n================================================================\n--><\/p>\n\n\n<p class=\"wp-block-paragraph\">Most useRef tutorials stop at one example \u2014 focusing an input \u2014 and leave the impression that <code>useRef<\/code> is just &#8220;the DOM access hook.&#8221; That&#8217;s true of maybe a third of what it&#8217;s actually for. The real idea behind <code>useRef<\/code> is simpler and more general: it gives a component a box that <strong>survives re-renders but never causes one<\/strong>. Sometimes that box holds a DOM node. Just as often it holds an interval ID, a previous value, a render count, or a method you want a parent to call directly. Once that distinction is clear, a whole category of &#8220;how do I do X in React&#8221; questions turns out to have the same one-line answer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The 8 demos below are all real, running React \u2014 drag a slider, start a ticker, click outside a menu \u2014 so you can watch each use of <code>useRef<\/code> actually behave instead of reading about it secondhand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every embed also has a <strong>Fork &amp; Edit<\/strong> button that opens the exact snippet, already running, in <a href=\"https:\/\/webdevpuneet.com\/ui-snippets\/mycode\/\">My Code<\/a> \u2014 webdevpuneet&#8217;s free in-browser editor. One note on the code you&#8217;ll see: these demos skip a build step, so the live JS uses <code>React.createElement<\/code> directly instead of JSX \u2014 the logic is identical to the JSX version you&#8217;d actually write, just without a compiler in between.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The one-sentence rule<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>const ref = useRef(initialValue);\n\nref.current;              \/\/ read or write it any time \u2014 never triggers a render\n\/\/ compare: setState(next) always schedules a render<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useRef(initialValue)<\/code> returns one plain object, <code>{ current: initialValue }<\/code>, and that same object is handed back on every render of the component \u2014 React never replaces it, never diffs it, never re-renders anything because you changed <code>.current<\/code>. That&#8217;s the entire mechanism. Everything below is just a different reason to want a box with that one property.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Focusing a Real DOM Node<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Click the button \u2014 it calls a real DOM method, <code>.focus()<\/code>, on the actual input element. No state, no re-render.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/1.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"Focusing a Real DOM Node Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const inputRef = useRef(null);\n\n\/\/ on the element: ref={inputRef}\ninputRef.current.focus();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the canonical use, and worth understanding precisely before the rest: passing a ref as an element&#8217;s <code>ref<\/code> prop tells React &#8220;once you&#8217;ve created the real DOM node for this element, put it in <code>ref.current<\/code>.&#8221; After the component mounts, <code>inputRef.current<\/code> <em>is<\/em> the actual <code>&lt;input&gt;<\/code> element sitting in the page \u2014 the same object <code>document.querySelector<\/code> would return \u2014 so any real DOM method (<code>.focus()<\/code>, <code>.scrollIntoView()<\/code>, <code>.click()<\/code>) works on it directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> focus management, scrolling an element into view, reading a canvas or video element to call its native API. <strong>Tip:<\/strong> <code>ref.current<\/code> is <code>null<\/code> until after the first render commits \u2014 never read it during the render itself, only inside event handlers or effects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. A Value That Doesn&#8217;t Trigger a Re-render<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both counters go up on every click. Only one of them makes the paragraph below actually update on screen.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/2.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"State vs Ref Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const &#91;stateClicks, setStateClicks] = useState(0);\nconst refClicks = useRef(0);\n\nsetStateClicks(stateClicks + 1);   \/\/ schedules a re-render\nrefClicks.current += 1;            \/\/ does not<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the demo that makes the core distinction impossible to unsee: the ref&#8217;s number is genuinely changing, every single click, exactly like the state&#8217;s number \u2014 you can prove it by forcing an unrelated re-render and watching the box update to the correct value it had all along. It just never <em>causes<\/em> a render on its own, so the screen only catches up once something else asks React to re-render.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> a value the UI never needs to reflect live \u2014 a click counter feeding an analytics batch that flushes every 10 seconds, a flag checked only inside an event handler. <strong>Tip:<\/strong> if you catch yourself reading <code>ref.current<\/code> to decide what to render, that&#8217;s a sign it should be state instead \u2014 refs are for values only imperative code ever needs to see.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. Remembering the Previous Value<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Drag the slider. The &#8220;previous&#8221; value is always one render behind \u2014 a ref updated inside an effect, after the render it&#8217;s shown in.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/3.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"usePrevious Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>function usePrevious(value) {\n  const ref = useRef();\n  useEffect(() =&gt; { ref.current = value; });\n  return ref.current;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The ordering here is the entire trick. During a given render, <code>ref.current<\/code> still holds whatever was stored on the <em>previous<\/em> render \u2014 the effect that updates it to the new value only runs <em>after<\/em> the DOM has committed, which is too late to affect the render that just happened. So the hook hands back &#8220;the value as of last time&#8221; precisely because writing the new value is deliberately deferred past the point where it could overwrite what this render needed to read.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> comparing a prop or state value against what it was last render \u2014 detecting a transition (was closed, now open), animating based on direction of change, or logging &#8220;changed from X to Y.&#8221; <strong>Tip:<\/strong> this exact 4-line hook is one of the most commonly hand-rolled utilities in React codebases \u2014 worth recognizing on sight in a codebase you didn&#8217;t write.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Fixing a Stale Closure in setInterval<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start the ticker, then flip the mode a few times before it logs. The &#8220;stale&#8221; side reports whatever mode was active when the interval was created; the &#8220;fixed&#8221; side always reports the mode right now.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/4.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"Stale Closure Fix Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const modeRef = useRef(mode);\nuseEffect(() =&gt; { modeRef.current = mode; }, &#91;mode]);\n\nuseEffect(() =&gt; {\n  const id = setInterval(() =&gt; {\n    console.log(modeRef.current);   \/\/ always current\n  }, 1200);\n  return () =&gt; clearInterval(id);\n}, &#91;]);   \/\/ intentionally not re-created when mode changes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The interval is set up once and deliberately not recreated on every <code>mode<\/code> change \u2014 recreating it would reset the timer&#8217;s own clock every time. But that means its callback&#8217;s own closure only ever sees the <code>mode<\/code> value from the render when it was created \u2014 a real, common bug, not a contrived one. The fix isn&#8217;t to add <code>mode<\/code> back to the dependency array; it&#8217;s to give the callback a ref it can read fresh every tick, updated by a <em>separate<\/em> effect that&#8217;s allowed to re-run as often as <code>mode<\/code> changes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> any long-lived callback \u2014 an interval, a WebSocket message handler, an IntersectionObserver callback \u2014 that needs to act on the latest value of something without being torn down and rebuilt every time that value changes. <strong>Tip:<\/strong> this pattern has a name, &#8220;the latest ref pattern,&#8221; and it&#8217;s worth building once as a small <code>useLatest(value)<\/code> hook rather than re-deriving it inline every time you hit this bug.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Uncontrolled Input \u2014 Read Once, Not on Every Keystroke<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Type in either field. The left one re-renders on every keystroke (watch the counter); the right one only reads its value when you click Submit.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/5.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"Controlled vs Uncontrolled Input Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const inputRef = useRef(null);\n\/\/ on the element: ref={inputRef} defaultValue=\"\"\n\nfunction handleSubmit() {\n  console.log(inputRef.current.value);   \/\/ read once, on demand\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A controlled input (<code>value<\/code> + <code>onChange<\/code>) re-renders the component on every keystroke, because React has to own the value to keep it in sync \u2014 for most fields that cost is invisible, but it&#8217;s a real cost. An uncontrolled input just lets the browser&#8217;s own DOM hold the value the whole time, the same as plain HTML always has, and a ref is only used to reach in and read <code>.value<\/code> the one moment it&#8217;s actually needed. Fewer renders, less code, at the price of not knowing the live value until you ask for it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> a field whose value only matters on submit \u2014 a search box, a one-shot form field, any input where you don&#8217;t need to validate or mirror the value as the user types. <strong>Tip:<\/strong> the moment you need live validation, a character counter, or to disable a button based on the current text, that&#8217;s the signal to switch that specific field back to controlled \u2014 the two approaches mix fine within one form.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Imperative Scrolling and Measuring<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The buttons call real DOM methods on the scroll container through a ref \u2014 nothing here is tracked in state, so scrolling itself never triggers a React re-render.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/6.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"Imperative Scrolling Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const boxRef = useRef(null);\n\nboxRef.current.scrollTo({ top: 0, behavior: 'smooth' });\nboxRef.current.scrollTop;         \/\/ read the current position, on demand\nboxRef.current.scrollHeight;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Scroll position is a textbook case for a ref rather than state: it changes dozens of times a second while a user drags or flicks, and almost nothing about a typical UI needs to re-render on every one of those changes. This demo never listens for a <code>scroll<\/code> event at all \u2014 it just reaches into the DOM node on demand, either to command it (<code>scrollTo<\/code>) or to read its current numbers (<code>scrollTop<\/code>, <code>scrollHeight<\/code>), exactly when a button click asks for it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> scroll-to-top buttons, &#8220;jump to section&#8221; navigation, infinite-scroll boundary checks triggered from a throttled listener rather than every frame. <strong>Tip:<\/strong> if you do need to react continuously to scroll position (a progress bar, a parallax effect), attach a real <code>scroll<\/code> event listener in an effect and throttle it \u2014 don&#8217;t try to poll a ref on a timer instead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. Click-Outside Detection with a Ref<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open the menu, then click anywhere outside it. A ref on the menu&#8217;s own DOM node is how the outside-click check knows what &#8220;outside&#8221; even means.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/7.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"Click Outside Detection Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const wrapRef = useRef(null);\n\nuseEffect(() =&gt; {\n  function onPointerDown(e) {\n    if (wrapRef.current &amp;&amp; !wrapRef.current.contains(e.target)) setOpen(false);\n  }\n  document.addEventListener('pointerdown', onPointerDown);\n  return () =&gt; document.removeEventListener('pointerdown', onPointerDown);\n}, &#91;open]);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The check itself is a single native DOM method, <code>.contains()<\/code> \u2014 &#8220;is the thing that was clicked inside this element?&#8221; \u2014 and a ref is what makes that element reachable from a document-level listener in the first place. The listener is only attached while the menu is actually open (the effect returns early otherwise), and it&#8217;s cleaned up the moment the menu closes or the component unmounts, so an already-closed menu never keeps a stray global listener running.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> dropdowns, popovers, custom select menus, any small floating panel that should dismiss itself the instant focus moves elsewhere. <strong>Tip:<\/strong> listen on <code>pointerdown<\/code> rather than <code>click<\/code> \u2014 it fires before focus moves, which avoids a class of bugs where clicking a different focusable element both closes the menu and immediately reopens something else in the same interaction.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. Exposing an Imperative API with useImperativeHandle<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The parent&#8217;s buttons never touch a DOM node directly \u2014 they call <code>.focus()<\/code> and <code>.clear()<\/code>, two methods the child component chose to expose on its own ref.<\/p>\n\n\n\n<iframe src=\"https:\/\/webdevpuneet.com\/demos\/a1\/13\/8.html\" style=\"width:100%;height:480px;border:0;border-radius:10px;overflow:hidden;\" loading=\"lazy\" title=\"useImperativeHandle Demo by webdevpuneet.com\"><\/iframe>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The rule:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const FancyInput = forwardRef((props, ref) =&gt; {\n  const innerRef = useRef(null);\n  useImperativeHandle(ref, () =&gt; ({\n    focus: () =&gt; innerRef.current.focus(),\n    clear: () =&gt; { innerRef.current.value = ''; innerRef.current.focus(); }\n  }));\n  return &lt;input ref={innerRef} \/&gt;;\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the one entry on this list where a component deliberately does <em>not<\/em> hand over its raw DOM node to whoever holds its ref. <code>forwardRef<\/code> lets a component accept a <code>ref<\/code> from its parent at all (in React 19 a function component can simply take <code>ref<\/code> as a normal prop, so <code>forwardRef<\/code> is optional there \u2014 this demo uses it because it runs on React 18 from a CDN), and <code>useImperativeHandle<\/code> replaces what that ref actually points to \u2014 instead of the real <code>&lt;input&gt;<\/code> element, the parent&#8217;s <code>fancyRef.current<\/code> becomes the small object returned here, with exactly the two methods the child decided to expose. The parent can call <code>.clear()<\/code>; it can&#8217;t reach in and read <code>.value<\/code> directly, because that was never part of the API the child chose to publish.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Best for:<\/strong> a reusable component (a custom input, a modal, a video player wrapper) that needs to offer a few specific imperative actions to its parent without exposing its entire internal DOM structure. <strong>Tip:<\/strong> reach for this only when a prop genuinely can&#8217;t express the interaction \u2014 most &#8220;imperative-seeming&#8221; needs (open\/closed state, current value) are still better modeled as a prop and a callback than as a ref method.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common pitfalls<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Reading <code>ref.current<\/code> during render to decide what to show.<\/strong> Refs don&#8217;t trigger re-renders, so a UI that depends on a ref&#8217;s value can silently go stale \u2014 demo #2 shows exactly this gap. If a value needs to affect what&#8217;s on screen, it belongs in state.<\/li>\n\n\n\n<li><strong>Mutating <code>ref.current<\/code> and expecting the screen to update on its own.<\/strong> Nothing is wrong with the mutation itself \u2014 the screen just won&#8217;t reflect it until some other state change forces a re-render, as demo #2&#8217;s &#8220;force a re-render&#8221; button demonstrates.<\/li>\n\n\n\n<li><strong>Recreating an interval or listener every time a dependency changes, just to avoid a stale closure.<\/strong> Demo #4 shows the better fix: keep the effect&#8217;s own lifecycle stable and give its callback a ref it can read fresh, instead of tearing down and rebuilding the whole subscription.<\/li>\n\n\n\n<li><strong>Attaching a click-outside listener that&#8217;s never removed.<\/strong> Demo #7&#8217;s effect only exists while <code>open<\/code> is true and always returns a cleanup function \u2014 skipping either half leaks a document-level listener per mount.<\/li>\n\n\n\n<li><strong>Reaching for <code>useImperativeHandle<\/code> when a prop would do.<\/strong> It&#8217;s the right tool for demo #8&#8217;s <code>.focus()<\/code>\/<code>.clear()<\/code> case specifically because there&#8217;s no natural prop for &#8220;do this now&#8221; \u2014 most component APIs are better served by props and callbacks than by ref methods.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Frequently asked questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Does changing ref.current ever cause a re-render?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No, never, by design \u2014 that&#8217;s the one property that defines what a ref is. Demo #2 proves it directly: the ref&#8217;s number changes every click exactly like the state&#8217;s number does, but the screen only updates once something else (state) triggers a render.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">When should I use useRef instead of useState?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When the value needs to persist across renders but the UI never needs to visually reflect it changing \u2014 a DOM node reference, an interval ID, a previous value for comparison, a render counter. The moment you need the screen to update when the value changes, it needs to be state, not a ref.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why is ref.current null on the first render?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Because the DOM node it will eventually point to doesn&#8217;t exist yet until after React commits the render to the actual page. Reading a DOM ref&#8217;s <code>.current<\/code> only makes sense inside an effect or an event handler, both of which run after that commit \u2014 never read it in the middle of a render function.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the difference between useRef and createRef?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>useRef<\/code> is the hook, for use inside function components \u2014 it returns the exact same ref object on every render of that component instance. <code>createRef<\/code> predates hooks and creates a brand-new ref object every time it&#8217;s called, which makes it unsuitable for use inside a function component&#8217;s body (it would reset every render); it&#8217;s still occasionally used in class components, where <code>useRef<\/code> isn&#8217;t available at all.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can a ref hold something other than a DOM node?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Yes \u2014 a ref is just a mutable box; nothing about it is DOM-specific. Demos #2 through #4 store a plain number, a previous value, or the latest mode for an interval \u2014 nothing DOM-related at all. Demos #1, #5, #6 and #7 put a DOM node inside one via the <code>ref<\/code> prop on an element, and demo #8 does too, hidden inside the child component.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every demo above ships its full HTML, CSS and JS in the <strong>HTML \/ CSS \/ JS<\/strong> tabs in its own top bar. The fastest way to actually internalize these is to click <strong>Fork &amp; Edit<\/strong> on demo #4 \u2014 the stale closure one \u2014 delete the <code>modeRef<\/code> fix, and watch the &#8220;fixed&#8221; column start reporting stale values too. Once you&#8217;ve got a version you like, save it to <a href=\"https:\/\/webdevpuneet.com\/ui-snippets\/mycode\/\">My Code<\/a> and it&#8217;s yours to keep, tweak further, or reuse in a real project.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Want to go deeper than a single embed? The <a href=\"https:\/\/webdevpuneet.com\/react-playground\/\">React Playground<\/a> is webdevpuneet&#8217;s full in-browser React sandbox \u2014 a real editor with instant preview, built for exactly this kind of edit-and-see learning, without the eight-tab constraint of a blog post.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most useRef tutorials stop at one example \u2014 focusing an input \u2014 and leave the impression that useRef is just &#8220;the DOM access hook.&#8221; That&#8217;s true of maybe a third of what it&#8217;s actually for. The real idea behind useRef is simpler and more general: it gives a component a box that survives re-renders but [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":156,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","advanced_seo_description":"React useRef explained with 8 live demos: focus, values that don't re-render, previous values, stale closures, uncontrolled inputs, click-outside and more.","jetpack_seo_html_title":"React useRef Explained: 8 Practical Uses Beyond the DOM","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[8,9],"tags":[],"class_list":["post-155","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-react","category-tutorials","et-has-post-format-content","et_post_format-et-post-format-standard"],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/phrh5V-2v","jetpack_featured_media_url":"https:\/\/i0.wp.com\/webdevpuneet.com\/blog\/wp-content\/uploads\/2026\/10\/react-useref-1200x630-compressed.jpg?fit=1200%2C630&ssl=1","_links":{"self":[{"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/posts\/155","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/comments?post=155"}],"version-history":[{"count":1,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/posts\/155\/revisions"}],"predecessor-version":[{"id":157,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/posts\/155\/revisions\/157"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/media\/156"}],"wp:attachment":[{"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/media?parent=155"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/categories?post=155"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webdevpuneet.com\/blog\/wp-json\/wp\/v2\/tags?post=155"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}