---
{
title: "React Refs: The Complete Story",
description: "",
published: '2020-10-24T05: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 defintions, 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 intial 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 Apromov](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 were to 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 it's 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 (
)
```
> Keep in mind, the `ref` attribute is added and handled by React on any HTML Element. This example uses a `div`, but this applies to `span`s and `header`s and beyond, "oh my".
In this example, if we took a look at the `console.log` in the `useEffect`, we'd find [an `HTMLDivElement` instance](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement) in the `current` property. Open the following StackBlitz and look at the console value to confirm:
Because `elRef.current` is now a `HTMLDivElement`, it means we now have access to [the entire `Element.prototype` JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/Element#Properties). As such, this `elRef` can be used to style the underlying HTML node:
```jsx
const elRef = React.useRef();
React.useEffect(() => {
elRef.current.style.background = 'lightblue';
}, [elRef]);
return (
)
```
## Alternative Syntax {#ref-function}
It's worth noting that the `ref` attribute also accepts a function. While [we'll touch on the implications of this more in the future](#callback-refs), just note that this code example does exaclty the same thing as `ref={elRef}`:
```jsx
const elRef = React.useRef();
React.useEffect(() => {
elRef.current.style.background = 'lightblue';
}, [elRef]);
return (
elRef.current = ref}/>
)
```
# Component References {#forward-ref}
- Can pass "ref" to components
- Property must not be called "ref"*
```jsx
const Container = ({children, divRef}) => {
return
}
const App = () => {
const elRef = React.useRef();
React.useEffect(() => {
console.log(elRef);
elRef.current.style.background = 'lightblue';
}, [elRef])
return (
);
```
https://stackblitz.com/edit/react-use-ref-effect-style-forward-ref-wrong-kinda
However, what happens if we try to switch to use the "ref" property name?
> This code does not function
```jsx
const Container = ({children, ref}) => {
return
}
const App = () => {
const elRef = React.useRef();
React.useEffect(() => {
console.log(elRef);
// The following line will throw an error:
// "Cannot read property 'style' of undefined"
elRef.current.style.background = 'lightblue';
}, [elRef])
return (
);
```
https://stackblitz.com/edit/react-use-ref-effect-style-forward-ref-wrong
- *Can get it to use the "ref" property
- Must use "forwardRef" API
- Props can still be accessed using the first property
```jsx
const Container = React.forwardRef(({children}, ref) => {
return
})
const App = () => {
const elRef = React.useRef();
React.useEffect(() => {
console.log(elRef);
elRef.current.style.background = 'lightblue';
}, [elRef])
return (
);
```
https://stackblitz.com/edit/react-use-ref-effect-style-forward-ref
# Add Data to Ref {#use-imperative-handle}
```jsx
import React from "react";
import "./style.css";
const Container = React.forwardRef(({children}, ref) => {
return
{children}
})
export default function App() {
const elRef = React.useRef();
React.useEffect(() => {
elRef.current.focus();
}, [elRef])
return (
Hello StackBlitz!
Start editing to see some magic happen :)
);
}
```
https://stackblitz.com/edit/react-use-imperative-handle-demo-pre
- useImperativeHandle hook allows properties to be added to ref
- Can be used in combination with forwardRef
- Only properties returned in second param are set to ref
```jsx
import React from "react";
import "./style.css";
const Container = React.forwardRef(({children}, ref) => {
const divRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
divRef.current.focus();
console.log("I have now focused");
}
}))
return
{children}
})
export default function App() {
const elRef = React.useRef();
React.useEffect(() => {
elRef.current.focus();
}, [elRef])
return (
Hello StackBlitz!
Start editing to see some magic happen :)
);
}
```
https://stackblitz.com/edit/react-use-imperative-handle-demo-post
> Be cautious about using this in production. It breaks unidirectional data binding
```jsx
React.useEffect(() => {
elRef.current.konami();
}, [elRef])
```
https://stackblitz.com/edit/react-use-imperative-handle-demo-useful
# React Refs in `useEffect ` {#refs-in-use-effect}
- `useEffect` only does the array check on re-render
- Ref's current property set doesn't trigger a re-render
```jsx
export default function App() {
const elRef = React.useRef();
React.useEffect(() => {
elRef.current.style.background = "lightblue";
}, [elRef]);
return (
Hello StackBlitz!
Start editing to see some magic happen :)
);
}
```
https://stackblitz.com/edit/react-use-ref-effect-style
However, what happens when you make the `div` render happen _after_ the initial render. What do you think will happen here?
```jsx
export default function App() {
const elRef = React.useRef();
const [shouldRender, setRender] = React.useState(false);
React.useEffect(() => {
if (!elRef.current) return;
elRef.current.style.background = 'lightblue';
}, [elRef.current])
React.useEffect(() => {
setTimeout(() => {
setRender(true);
}, 100);
}, []);
return !shouldRender ? null : (
Hello StackBlitz!
Start editing to see some magic happen :)
);
}
```
https://stackblitz.com/edit/react-use-ref-effect-bug-effect
```jsx
const [minus, setMinus] = React.useState(0);
const ref = React.useRef(0);
const addState = () => {
setMinus(minus + 1);
};
const addRef = () => {
ref.current = ref.current + 1;
};
React.useEffect(() => {
console.log(`ref.current:`, ref.current);
}, [ref.current]);
React.useEffect(() => {
console.log(`minus:`, minus);
}, [minus]);
```
https://stackblitz.com/edit/react-use-ref-not-updating
Here are some comments from Dan Apromov, of the React Core team:
https://github.com/facebook/react/issues/14387#issuecomment-503616820
https://twitter.com/dan_abramov/status/1093497348913803265
https://github.com/facebook/react/issues/14387#issuecomment-493677168
But what does Dan mean by "callback ref"?
# Callback Refs {#callback-refs}
- Remember, "ref" property accepts a function to access the node
- Because it's not using "useEffect", code can execute in proper timing
```jsx
const elRefCB = React.useCallback(node => {
if (node !== null) {
node.style.background = "lightblue";
}
}, []);
return !shouldRender ? null : (
Hello StackBlitz!
Start editing to see some magic happen :)
);
```
https://stackblitz.com/edit/react-use-ref-callback-styling
- Can still keep a traditional "useRef" reference
```jsx
const elRef = React.useRef();
console.log("I am rendering");
const elRefCB = React.useCallback(node => {
if (node !== null) {
node.style.background = "lightblue";
elRef.current = node;
}
}, []);
React.useEffect(() => {
console.log(elRef.current);
}, [elRef, shouldRender]);
```
https://stackblitz.com/edit/react-use-ref-callback-and-effect
# `useState` Refs {#usestate-refs}
- Combining useState and Callback Refs
- Will trigger a re-render
- Works in useEffect
```jsx
const [elRef, setElRef] = React.useState();
console.log('I am rendering');
const elRefCB = React.useCallback(node => {
if (node !== null) {
setElRef(node);
}
}, []);
React.useEffect(() => {
console.log(elRef);
}, [elRef])
```
https://stackblitz.com/edit/react-use-ref-callback-and-use-state
- Can be used to impact reference using useEffect instead of inside of callback
```jsx
const [elNode, setElNode] = React.useState();
const elRefCB = React.useCallback(node => {
if (node !== null) {
setElNode(node);
}
}, []);
React.useEffect(() => {
if (!elNode) return;
elNode.style.background = 'lightblue';
}, [elNode])
```
https://stackblitz.com/edit/react-use-ref-callback-and-state-effect
# Conclusion