useRef
useRef is like class instance variable for function components.
Last updated
useRef is like class instance variable for function components.
Last updated
When using React, you may require an escape hatch to write imperative-style code in order to interact directly with DOM elements. You can do this by using React's createRef method!
React.createRef
allows you to obtain references to DOM nodes (). It's really just a JavaScript equivalent of this all-too-familiar snippet:
This is exactly what React.createRef()
does, although it requires a bit of a different setup.
When working with class-based components, we used createRef()
to create a ref. However, since React now recommends functional components and general practice is to use Hooks, we no longer need to use createRef().
To create refs in functional components, we instead use useRef(null).
The useRef
Hook in React can be used to directly access DOM nodes, as well as persist a mutable value across rerenders of a component.
The basic signature for the useRef
Hook looks like this:
useRef
returns a mutable object whose value is set as: {current: initialValue}
.
The reference object is mutable which means we can access the reference value using reference.current
and update it by assigning reference.current to a new value or variable.
There are two important behaviours to remember about useRef() references:
The reference value stays the same (persists) between component re-renders.
Updating a reference value does not re-render the component.
The difference between using useRef
and manually setting an object value directly within your component, e.g., const myObject = {current: initialValue}
, is that the ref
object remains the same all through the lifetime of the component, i.e., across re-renders.
To update the value stored in the ref
object, you go ahead and mutate the current
property as follows:
The returned object from invoking useRef
will persist for the full lifetime of the component regardless of re-renders.
const reference = useRef(0
) creates a reference reference with an initial value of 0. This reference object is used to keep track of how many times a button has been clicked. The reference value is updated and logged to the console when you click the button. As you may have noticed in your console, "Component rendered" is only logged once (during the initial render), implying that button clicks, or more precisely, reference value updates, do not cause component re-renders.
We could use useRef in conjunction with the ref
attribute to obtain the underlying DOM nodes and perform DOM operations imperatively. In reality, this is an escape route. We should only do this sparingly for things like focus management, for which React does not provide a declarative API.
Declarative views are one of the many concepts popularized by React among developers. Prior to declarative views, most of us changed the DOM by calling functions that changed it explicitly.
We are now declaring views based on a state, and while we are still calling functions to change this state, we have no control over when or if the DOM will change.
If it weren't for the refs, we'd lose this imperative nature due to the inversion of control.
A common use case for useRef
is to manage child DOM nodes:
The example above works because if you pass a ref
object to React, e.g., <div ref={myRef} />
, React will set its current
property to the corresponding DOM node whenever that node changes, i.e., myRef = {current: *dom node*}
.
useRef
returns a plain JavaScript object, so it can be used for holding more than just DOM nodes.
It can hold whatever value you want. This makes it the perfect choice for simulating instance-like variables in functional components:
In the example above, we log initialProp
and prop
via useEffect
. This will be logged on mount and every time prop
changes.
Since initialProp
is prop
saved on initial render, it never changes. It’ll always be the initial value of props
. Here’s what we mean.
If prop
passed to App
were changed from 1
to 5
, coming from state update , the following will be logged:
initialProp
remains the same through the lifetime of the component because it is saved in the ref
object. The only way to update this value is by mutating the current property of the ref
object: initialProp.current =
*new value*
.
With this, you can go ahead and create instance-like variables that don’t change within your functional component.
Remember, the only difference between useRef
and creating a {current: ...}
object yourself is that useRef
will give you the same ref
object on every render.
As good as React is, there are many utilities and libraries that have been in use on the web for years that are not part of its ecosystem. Using refs, for example, allows us to combine React with a fantastic animation library. It's a good idea to use their stability and resolution for some specific problems.
The GSAP library is a popular choice for animation examples. To use it, we need to send a DOM element to any of its methods.
When the element gets unmounted, we’ll clean the DOM state and actions by terminating any ongoing animation with the kill()
method supplied by the Timeline
instance.
During initial rendering, the reference supposed to hold the DOM element is empty:
During initial rendering React still determines what is the output of the component, so there's no DOM structure created yet. That's why inputRef.current
evaluates to undefined
during initial rendering.
useEffect(callback, [])
hook invokes the callback right after mounting, when the input element has already been created in DOM.
callback
function of the useEffect(callback, [])
is the right place to access inputRef.current
because it is guaranteed that the DOM is constructed.
The function scope of the functional component should either calculate the output or invoke hooks.
That's why updating a reference (as well as updating state) shouldn't be performed inside the immediate scope of the component's function.
The reference must be updated either inside a useEffect()
callback or inside handlers (event handlers, timer handlers, etc).
useRef
vs. createRef
createRef every time when it runs, it creates a new ref object.
useRef
only creates a ref object for a particular component instance when it's first rendered. In the following rerenders, it'll just returns the existing ref object associated with that component instance. That's why we can trust it to persist a value across rerenders!
There’s one more thing to note. useRef
doesn’t notify you when its content changes, i.e., mutating the current
property doesn’t cause a re-render. For cases such as performing a state update after React sets the current property to a DOM node, make useCallback ref as follows:
Updating state does trigger component re-rendering but updating a reference does not.
The state update is asynchronous (state variable is updated after re-rendering - read more why is it async ), while the reference update is synchronous.
So what is forwardRef, and why is it important?
Let's start with an example: Let's say we have an Input
component in our React application that looks like the following:
Now, we also want to focus the input component on the click of a button.
To achieve that, we simply need to create a new reference in our app, pass it down to Input
, and call .focus()
on the element, right? Wrong!
By default, the ref
prop only works on HTML elements, not on React components.
When we want to pass down a reference to a React component, we need to tell React which HTML element it should reference, as there can be more than one in our component.
That's where forwardRef
becomes useful. It allows us to specify which exact HTML element we want to reference.
Our task is to forward the ref that the Input
component receives to the HTML input element.
We do that by wrapping the component in forwardRef
. The function accepts a callback with two parameters:
The component props
The ref to be forwarded
This callback is our function component, which now accepts a second property.
In the returned JSX code, we now need to pass the ref we receive in the function to the correct HTML component, which in our case is the input
element.
Here's the final code for doing so:
The forwardRef
function takes the name of the component from the function it accepts as a parameter. If you're using anonymous or arrow functions, your DevTools will display the component like this:
There are a few things you can do to avoid this. To begin, you could use a regular function expression with the following name:
If, like me, you haven't used this syntax in the last three years, you can manually set the display name by adding the following line to the end of the file: