--- { title: "React Refs: The Complete Story", description: "React Refs are an immensely powerful, yet often misunderstood API. Let's learn what they're capable of, and how they're usually misused.", published: '2020-12-01T05:12:03.284Z', authors: ['crutchcorn'], tags: ['react', 'javascript'], attached: [], license: 'cc-by-nc-sa-4' } --- Programming terminology can be rather confusing. The first time I'd heard about "React Refs", it was in the context of [getting a reference to a DOM node](#dom-ref). However, with the introduction of hooks, the `useRef` hook has expanded the definition of "refs". Today, we'll be walking through two definitions of refs: - A [mutable data property](#use-ref-mutate) to persist data across renders - A [reference to DOM elements](#dom-ref) We'll also be exploring additional functionality to each of those two definitions, such as [component refs](#forward-ref), [adding more properties to a ref](#use-imperative-handle), and even exploring [common code gotchas associated with using `useRef`](#refs-in-use-effect). > As most of this content relies on the `useRef` hook, we'll be using functional components for all of our examples. However, there are APIs such as [`React.createRef`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs) and [class instance variables](https://www.seanmcp.com/articles/storing-data-in-state-vs-class-variable/) that can be used to recreate `React.useRef` functionality with classes. # Mutable Data Storage {#use-ref-mutate} While `useState` is the most commonly known hook for data storage, it's not the only one on the block. React's `useRef` hook functions differently from `useState`, but they're both used for persisting data across renders. ```jsx const ref = React.useRef(); ref.current = "Hello!"; ``` In this example, `ref.current` will contain `"Hello!"` after the initial render. The returned value from `useRef` is an object that contains a single key: `current`. If you were to run the following code: ```jsx const ref = React.useRef(); console.log(ref) ``` You'd find a `{current: undefined}` printed to the console. This is the shape of all React Refs. If you look at the TypeScript definition for the hooks, you'll see something like this: ```typescript // React.d.ts interface MutableRefObject { current: any; } function useRef(): MutableRefObject; ``` Why does `useRef` rely on storing data inside of a `current` property? It's so that you can utilize JavaScript's "pass-by-reference" functionality in order to avoid renders. Now, you might think that the `useRef` hook is implemented something like the following: ```jsx // This is NOT how it's implemented function useRef(initial) { const [value, setValue] = useState(initial); const [ref, setRef] = useState({ current: initial }); useEffect(() => { setRef({ get current() { return value; }, set current(next) { setValue(next); } }); }, [value]); return ref; } ``` However, that's not the case. [To quote Dan Abramov](https://github.com/facebook/react/issues/14387#issuecomment-493676850): > ... `useRef` works more like this: > > ```jsx > function useRef(initialValue) { > const [ref, ignored] = useState({ current: initialValue }) > return ref > } > ``` Because of this implementation, when you mutate the `current` value, it will not cause a re-render. Thanks to the lack of rendering on data storage, it's particularly useful for storing data that you need to keep a reference to but don't need to render on-screen. One such example of this would be a timer: ```jsx const dataRef = React.useRef(); const clearTimer = () => { clearInterval(dataRef.current); }; React.useEffect(() => { dataRef.current = setInterval(() => { console.log("I am here still"); }, 500); return () => clearTimer(); }, [dataRef]); ``` # Visual Timer with Refs {#visual-timers} While there are usages for timers without rendered values, what would happen if we made the timer render a value in state? Let's take the example from before, but inside of the `setInterval`, we update a `useState` that contains a number to add one to its state. ```jsx const dataRef = React.useRef(); const [timerVal, setTimerVal] = React.useState(0); const clearTimer = () => { clearInterval(dataRef.current); } React.useEffect(() => { dataRef.current = setInterval(() => { setTimerVal(timerVal + 1); }, 500) return () => clearInterval(dataRef.current); }, [dataRef]) return (
{timerVal}
); ``` Now, we'd expect to see the timer update from `1` to `2` (and beyond) as the timer continues to render. However, if we look at the app while it runs, we'll see some behavior we might not expect: This is because [the closure](https://whatthefuck.is/closure) that's passed to the `setInterval` has grown stale. This is a common problem when using React Hooks. While there's a simple solution hidden in `useState`'s API, let's solve this problem using mutations and `useRef`. Because `useRef` relies on passing by reference and mutating that reference, if we simply introduce a second `useRef` and mutate it on every render to match the `useState` value, we can work around the limitations with the stale closure. ```jsx const dataRef = React.useRef(); const [timerVal, setTimerVal] = React.useState(0); const timerBackup = React.useRef(); timerBackup.current = timerVal; const clearTimer = () => { clearInterval(dataRef.current); }; React.useEffect(() => { dataRef.current = setInterval(() => { setTimerVal(timerBackup.current + 1); }, 500); return () => clearInterval(dataRef.current); }, [dataRef]); ``` > * I would not solve it this way in production. `useState` accepts a callback which you can use as an alternative (much more recommended) route: > > ```jsx > const dataRef = React.useRef(); > > const [timerVal, setTimerVal] = React.useState(0); > > const clearTimer = () => { > clearInterval(dataRef.current); > }; > > React.useEffect(() => { > dataRef.current = setInterval(() => { > setTimerVal(tVal => tVal + 1); > }, 500); > > return () => clearInterval(dataRef.current); > }, [dataRef]); > ``` > We're simply using a `useRef` to outline one of the important properties about refs: mutation. # DOM Element References {#dom-ref} At the start of this article, I mentioned that `ref`s are not just a mutable data storage method but a way to reference DOM nodes from inside of React. The easiest of the methods to track a DOM node is by storing it in a `useRef` hook using any element's `ref` property: ```jsx const elRef = React.useRef(); React.useEffect(() => { console.log(elRef); }, [elRef]); return (Start editing to see some magic happen :)
Start editing to see some magic happen :)
>Start editing to see some magic happen :)
{displayTxt}
{displayTxt}
Start editing to see some magic happen :)
Start editing to see some magic happen :)
Start editing to see some magic happen :)
Start editing to see some magic happen :)
Start editing to see some magic happen :)