React useRef
is a hook that allows a function component to create a reference to a value and track its state between component re-renders. This is made possible because a ref
object is able to persist its value so that it will not lose track of the value it references during component re-render. The reference can also be used to update the value that is being tracked.
In addition to preserving values between re-renders, the useRef
hook is also used to create references to DOM elements and access their values or perform manipulations on them. For example, we can create reference to form input elements and extract their values after the user submits the form.
In contrast to state variables, ref
objects are mutable. This implies that we can directly modify the values tracked by a ref
object without the need for a special function. Updates to ref
values do not trigger a re-render of the containing function component, making them very efficient.
In this post, we will demistify React useRef
hook with clear examples to better understand its capabilities and usage.
Table of Contents
What is the use of useRef Hook?
Basically, React useRef
hook is used to create a reference to a value and preserve its state so that the value will not be lost when the function component re-renders. Additionally, it can be used to keep reference to a DOM element and retrieve or manipulate its properties such as content, appearance, etc.
To better understand what we mean by preserving the state of a variable, lets’ look at the general design of a javascript function.
Traditionally, creating a value in a javascript function involves a variable declaration statement such as the following:
function logCallsCount() {
// initialise counter variable
let callsCount = 0;
}
Any updates to values initialised within the function, such as the value of callsCount
in listing 1, are retained as long as the function is still running. In other words, the function will remember the state of all variables declared within it as long as it has not exited:
Consider the following code listing:
function logCallsCount() {
// initialise counter variable
let callsCount = 0;
console.log(`Calls count is: ${count}`)
// update calls counter
callsCount += 1;
console.log(`Calls count is: ${count}`)
}
// call the function to log calls count
logCallsCount();
A single call to logCallsCount()
on line 12
will produce the following in the console, which is exactly what we expect:
A general design of javascript functions, and in most programming languages, is that when a function completes its task and exits, the values of its variables are lost. Subsequent function calls will not remember the values of the variables in the previous calls. The values of the variables are reset when the function is called again and begins execution.
The following code listing calls logCallsCount()
twice.
// call the function to log calls count
logCallsCount();
logCallsCount();
The output of the code is shown below:
In the first call to logCallsCount()
, the value of callsCount
was 1
before the function exited. When the second call to logCallsCount()
begins execution, it does not remember that the value of callsCount
was 1
when the first call to logCallsCount()
exited. Therefore, callsCount
is reset to the value 0
before being incremented by 1
. Hence, in each call to logCallsCount()
, the same values are logged to the console.
In modern React applications development, function components are widely used. When part of the application UI needs to be updated, React calls the function component to refresh and update the UI, a process commonly referred to as re-render. Thus, a function component can be called multiple times to perform UI update. This re-render of components causes local variables to be reset to initial values, losing any values they contained before the re-render, as demonstrated earlier.
To address this challenge, React uses hooks such as useState
, useReducer
, and useRef
hooks to create a reference to a value and track its state so that its value will not be lost when the function component re-renders. In this post, we will focus on how to preserve values and access DOM elements with useRef
hook. The section on understanding useState hook tackles how to preserve and update values with useState
hook.
Creating a Reference with useRef
To create a reference to a value that can be preserved between component re-renders, or access a DOM element, we first need to import useRef
from React:
import { useRef } from 'react';
Once imported, we can create a reference variable by calling useRef()
:
let clicksCount = useRef();
In the above code listing, we declare clicksCount
as a reference variable and initialize it with a call to useRef()
. As indicated earlier, this gives clicksCount
the ability to preserve the value it references so that when the function component in which it is defined re-renders, clicksCount
can remember its state from the previous render.
useRef
is a hook and therefore its usage must comply with the rules of hooks in React. For example, initialization of a ref
object with useRef()
must be done at the beginning of the body of the function component.
Mostly, it is appropriate to set an initial value when we declare a variable. For example:
let clicksCount = 0;
We can similarly set an initial value when we declare a ref
variable in React application. To do this, we pass the intial value as an argument to useRef()
:
let clicksCount = useRef(0);
Just as we can assign values of any javascript type to a variable, we can also assign any value such as number
, string
, array
, object
, etc by passing it to useRef()
:
let numberRef = useRef(20);
let stringRef = useRef('Hello React');
let arrayRef = useRef( ['React', 'Javascript'] );
let objectref = useRef( {library: 'React', language: 'Javascript'} );
let undefinedRef = useRef(undefined);
let nullRef = useRef(null);
If we do not pass an initial value to useRef()
, then the value of the reference variable will be undefined
.
Now that we know how to initialize a reference variable, let’s compare a regular javascript variable declaration with a React reference variable declaration:
// declaration of regular javascript variable
let callsCount = 5;
// declaration of React reference variable
let clicksCount = useRef(5);
As can be seen, creating a ref
variable in React isn’t much of a pain. We only need to set the ref
variable to useRef()
, while passing an optional initial value as argument to useRef()
. Other than this, the two declarations are syntactically close.
Ref Variables are Objects
A ref
variable is actually an object which contains a single property named current
. This is because a call to useRef()
returns an object, with the optional initial value set as value to the current
property:
{ current: initialValue }
Whereas we can access the value of regular javascript variable directly, we can only access the value of a ref
variable through its current
property:
// declaration of a ref variable
let clicksCount = useRef(0);
// access the value tracked by the ref variable
console.log(clicksCount.current);
As can be seen, we are able to access the value referenced by the ref
object through the current
property.
Ref Objects are Mutable
Unlike state variables initialized with useState() which must be treated as immutable, ref
variables are mutable. This implies that we can directly modify a ref
object without the need for a special function, or set them to a newly initialized object data. Therefore, we can update the value of the ref
object by directly setting the new value to the current
property:
import React, { useRef } from 'react';
const ClicksCounter = () => {
// declaration of a ref variable
let clicksCount = useRef(0);
// update the value of the ref object
clicksCount.current = 5;
clicksCount.current += 1;
// access the value tracked by the ref variable
console.log(clicksCount.current); // will output 6
return <></>
}
export default ClicksCounter;
Observe that on lines 8 and 9, we modified the value referenced by the ref
object through the current
property.
Ref Updates Do Not Re-render
When the value referenced by a ref
object is updated, React does not call the function component to re-render and update the application UI. Due to this, a ref
object value is not suitable in UI code. This is because the application view will not be updated to reflect current values of ref
objects until an update to a state variable has been done to trigger a re-render. Thus, ref
objects are not expected to be used in JSX code.
In some cases, using ref
objects in JSX may work, but this may lead to unexpected application behaviour. For best practices, we should avoid ref
objects in JSX. If we need values that need to be preserved and also be used in the UI, then we need to initialize the values with useState()
.
Preserving Values with useRef
As indicated earlier, one of the uses of useRef
hook is to preserve some values so that the values can be recoverd when the function component re-renders. It is ideal for storing values that should not trigger a re-render when the values are updated. This is in contrast to state variables which trigger a re-render when updated.
Consider the following code listing which defines a ref
variable and a regular javascript variable. The code demonstrates how a regular javascript variable differs from a ref
variable.
To confirm that a ref
object persists its value when a component re-renders, we will introduce a state variable which wie will intentionally update in order to trigger a component re-render.
import React, { useRef, useState } from "react";
const ClicksCounter = () => {
const [count, setCount] = useState(0);
let refCounter = useRef(0); // ref variable
let nonRefCounter = 0; // regular variable
console.log(`Ref count is: ${refCounter.current}`);
console.log(`Non-ref count is: ${nonRefCounter}`);
const onBtnClick = () => {
refCounter.current += 1;
nonRefCounter += 1;
// To trigger a re-render, we intentionally
// update a state variable
setCount(refCounter.current);
}
return (
<div style={{width: '50%', margin: '20px auto'}}>
<button onClick={onBtnClick}>Click Me</button>
</div>
)
}
export default ClicksCounter;
On lines 5 and 6, we declare a ref
variable and a regular javascript variable respectively. The intent is to demonstrate that a regular javascript variable will lose track of its value whiles a ref
variable will remember its previous value during component re-render.
When the button
is clicked, we update both refCounter
and nonRefCounter
in the onBtnClick
event handler. To confirm that a ref
object persists its value when a component re-render occurs, we intentionally update the state variable, in this case count
, in order to trigger a re-render. The updated values are then logged to the console. We can see the result below:
As can be seen, React calls the ClicksCounter
component to re-render when the button is clicked. That is because we perform an update to a state variable in the event handler.
You should notice from the log that refCounter
persists its previous value and uses it during re-render. It is therefore able to increment its value when the button is clicked. However, nonRefCounter
, which is a regular javascript variable, is not able to persist its value and recall it during re-render. Hence, nonRefCounter
always resets to the initial value, outputting the value 0
.
We have said that an update to the value referenced by a ref
object does not trigger a re-render. In the previous example, a re-render of the component occurs because we intentionally did so by updating a state variable.
To prove that an update to a ref
object does not cause a component re-render, remove or comment out line 17 in listing 13. This time, there will be no log output when the button is clicked. The ClicksCounter
component isn’t called to re-render, hence line 8 and line 9 aren’t executed again.
Accessing DOM Elements
At this point, we have a very firm understanding of the use of useRef
hook in React applications development. It is used to store a value, or object, so that the value it references will not be lost when a function component in which it is defined re-renders.
In addition to persisting values between component re-renders, React useRef
hook can be used to create a reference to a DOM element, eliminating the need for javascript libraries such as jQuery. Although it is possible to use jQuery in React applications, if its requirement is basically for the purpose of referencing and or manipulating the DOM, then jQuery isn’t needed. React useRef
hook does the job well. It can be used to reference DOM elements and manipulate their contents.
Let’s consider a very simple React function component that displays an input
control and a button
: We will enter a name in the input
control and click on the button
.
import React, { useRef } from "react";
const MyName = () => {
return (
<div style={{width: '60%', margin: '20px auto', textAlign: 'center'}}>
<input type='text' placeholder="Enter your name" />
<button>Submit</button>
</div>
)
}
export default MyName;
A preview of the MyName
component, when rendered in the browser, should look similar to the following:
What we intend to do is to get and display the text entry after the Submit
button is clicked. To do this, we will need a way to reference the input
control so that we can retrieve its value
after the button is clicked.
If you have worked with the DOM API before, then you know that we will need to get a reference to the input
control. In the non-react way, it is done by specifying an id
attribute on the element:
<input type='text' id='myName' />
To get a reference to the input
control dynamically, we call document.getElementById()
, or use jQuery
javascript library to return it:
// get reference to the input control using DOM api
let myNameInput = document.getElementById('myName');
// get reference to the input control using jQuery
let myNameInput2 = $('#myName');
Although these methods will work in React, they require that we run codes like these in an effect function using React useEffect hook. For detailed coverage of React useEffect
hook, read on the concise discussion on understanding react useEffect hook.
To reference a DOM element in the React way, we need to add ref
attribute to the element of concern, and then initialize a ref
object with useRef()
. Rather than specify an id
attribute on the DOM element, we will specify a ref
attribute and set its value to the ref
object that we initialized.
import React, { useRef } from "react";
const MyName = () => {
// initialize a ref object with useRef
const myNameRef = useRef();
return (
<div style={{width: '60%', margin: '20px auto', textAlign: 'center'}}>
<input type='text' placeholder="Enter your name" ref={myNameRef} />
<button>Submit</button>
</div>
)
}
export default MyName;
As can be seen, we initialize ref
variable, myNameRef
, on line 5 with useRef()
. Then on line 9, we have specified a ref
attribute rather than id
attribute on the input
element. We have also set the value of the ref
attribute to myNameRef
which we initialized on line 5.
Recall what a ref
variable or object does. It can track or preserve the value it references, even when the function component in which it is defined re-renders. This means that myNameRef
will always remember that it references the input
element. It will not lose track of the element that it references.
The following code listing gets and displays the value of the input
control after the button is clicked:
import React, { useRef } from "react";
const MyName = () => {
// initialize a ref object with useRef
const myNameRef = useRef();
// event handler for button click
const onBtnClick = () => {
alert(`Your name is ${myNameRef.current.value}`);
}
return (
<div style={{width: '60%', margin: '20px auto', textAlign: 'center'}}>
<input type='text' placeholder="Enter your name" ref={myNameRef} />
<button type='submit' onClick={onBtnClick}>Submit</button>
</div>
)
}
export default MyName;
Remember that the value referenced by a ref
object can be accessed through its current
property. Hence, the current
property of myNameRef
references the input
element. As seen on line 14, we set myNameRef
as value to the ref
attribute. In the DOM API, the input
element is actually an object of HTMLInputElement
.
Since myNameRef.current
references an HTMLInputElement
, we obtain the text entered by the user through the value
property. If we wanted to save the text entered by the user, we would simply assign the value
property of the element to another variable:
// save the text entered by the user
const name = myNameRef.current.value;
One thing you should realize is that we are able to reference DOM elements for dynamic data retrieval without the need for DOM API functions like document.getElementById()
, document.getElementsByClassName()
, or document.querySelector()
. Also, there is no need to use jQuery library. For DOM API or jQuery, we would need to specify an id
attribute on HTML element, and then retrieve the value in a useEffect hook
function, such as in the following:
useEffect(() => {
// get reference to input and retrieve value with DOM API
const inputElem = document.getElementById('myName');
const name = inputElem.value;
// alternatively with jQuery
// get reference to input and retrieve value with jQuery
const inputElem2 = $('#myName');
const name2 = $(inputElem2).val();
}, []);
However, when using useRef
to reference elements, we rather need to specify a ref
attribute and set its value to a ref
object initialized with useRef()
. In this case, there will be no need for an id
attribute on the element.
Manipulating DOM Elements
Sometimes, we may need to dynamically manipulate DOM elements, such as update their content or apply styles to them. In such cases, we may need to get a reference to the DOM elements that need to be updated dynamically.
Luckily for us, we have already covered the initial part of manipulating DOM elements: getting a reference to DOM elements. The previous section on Referencing DOM Elements, which is a precursor to the discussion in this section, already dealt with this first part.
In listing 18, we displayed the name entered by the user with the javascript alert()
function. Let’s consider an alternative approach in which we dynamically update a DOM element to display the name in the document page.
import React, { useRef } from "react";
const MyName = () => {
// initialize a ref object with useRef
const nameInputRef = useRef();
const nameRef = useRef('');
// event handler for button click
const onBtnClick = () => {
nameRef.current.innerText = `Your name is ${nameInputRef.current.value}`;
}
return (
<div style={{width: '60%', margin: '20px auto', textAlign: 'center'}}>
<input type='text' placeholder="Enter your name" ref={nameInputRef} />
<button type='submit' onClick={onBtnClick}>Submit</button>
<p ref={nameRef}></p>
</div>
)
}
export default MyName;
On line 17, we introduce a <p></p>
tag which will be used to display information about the name entered by the user. The content of this <p></p>
tag is blank, hence no text is initially displayed. To be able to reference it after render, we set its ref
attribute to nameRef
, a ref
object initialized on line 6 with useRef()
.
The following is a rendered view and usage of MyName
component in listing 21:
We have said that when a ref
object is set as value to a ref
attribute of a DOM element, its current
property references the DOM element. For nameRef
, it references the <p></p>
tag, and we can, for example, dynamically set its content with innerText
property. We do this on line 10 in the onBtnClick
event handler, as shown below:
nameRef.current.innerText = `Your name is ${nameInputRef.current.value}`;
If you are used to working with the DOM API, you can use ref
objects to access DOM objects for manipulating the document. For example, knowing that nameRef
references DOM element p
in listing 21, we can change its font style with the following code:
nameRef.current.style.fontStyle = 'italic';
You will rarely see code such as this in React applications. We are only demonstrating that ref
object can be used as a reference to a DOM element, which eliminates the need for DOM API calls like document.getElementById()
, document.querySelector()
, etc.
For instance, rather than set the content of the DOM element p
with innerText
property, we can use React useState
hook to update its content. The following code listing is an example:
import React, { useRef, useState } from "react";
const MyName = () => {
// initialize a ref object with useRef
const nameInputRef = useRef();
const [nameInfo, setNameInfo] = useState('');
// event handler for button click
const onBtnClick = () => {
setNameInfo(`Your name is ${nameInputRef.current.value}`);
}
return (
<div style={{width: '60%', margin: '20px auto', textAlign: 'center'}}>
<input type='text' placeholder="Enter your name" ref={nameInputRef} />
<button type='submit' onClick={onBtnClick}>Submit</button>
<p>{nameInfo}</p>
</div>
)
}
export default MyName;
It is not recommended to use the value of a ref
object directly in JSX code. For example, replcacing line 17 of listing 24 with the following isn’t recommended:
<p>{nameInputRef.current.value}</p>
We should try as much as possible to avoid using the value of ref
objects in application UI code.
When to use useRef
In this section, we outline some of the use cases of React useRef
hook.
- Persist Values Without Re-render: The
useRef
hook should be used when there is the need to preserve some values between re-renders and when at the same time, we do not want to trigger a component re-render after we update the value. If there is the need to trigger a component re-render for the updated value to be visible in the UI, thenuseRef
isn’t the right hook. In this case,useState
has to be used. - Access DOM Element Values: The
useRef
hook can be used to dynamically retrieve values from DOM elements. For example, after a user clicks on the submit button of a form, the user’s input can be retrieved through references to each form input. For form inputs, usinguseRef
to retrieve the values can be very efficient. IfuseState
is used, it will trigger a re-render for every character the user types. - Manipulate DOM Element: As demonstrated in listing 21, DOM elements can be manipulated with the use of the
useRef
hook, for example their content or styles. However, the content of DOM elements are commonly updated with state variables initialized withuseState
hook.
Key Considerations
When working with the useRef
hook, there are some factors that need to be considered. Complying with ensure best performance and reliability of React applications and also conform to best programming practices.
Avoid Refs in JSX
For React best practices, it is highly recommended to avoid the use of ref
object values directly in JSX. If there is the need to display variables in the application UI, then state variables have to be used.
For example, the following code isn’t recommended:
import React, { useRef } from 'react';
const MyComponent = () => {
const refObject = useRef(10);
return (
<div>You are {refObject.current} years old.</div>
)
}
export default MyComponent;
On line 7 a ref
object value is directly used in the JSX code. This practice isn’t recommended. If there is the need to use variables in JSX, then the recommended approach is to use state variables in the UI code.
Avoid Recreating Initial Values
When initializing a ref
object with useRef()
, it is important to avoid passing functions that perform resource intensive tasks as argument to the useRef()
call. For example:
const refObject = useRef(getInitialValue());
Keep in mind that the initial value passed to useRef()
is used and an assigned to the ref
object’s current
property only in the first render of the component. For re-render, the initial value is not used. It is rather the value that was persisted before the re-render which is used.
If we pass a function to useRef()
, the function will be called on every re-render. However, its return value will not be used when the component re-renders. This becomes a waste of processing time and resource if the function performs expensive task.
If we need to call a function to get the initial value at the first render, we can pass null
to useRef()
, and then use conditional logic to determine if the initial value was initially set or not:
const refObject = useRef(null);
// Check to see if current property is null,
// then we call the function. If not null, then
// it means the initial value was previously set
if (refObject.current === null) {
refObject.current = getInitialValue();
}
Reading and Writing Refs
We have earlier indicated that ref
object values need not be used in the JSX code. It is highly recommended that we read or write to ref
objects in event handlers and or effect handlers.
For example, in listing 13, we updated refCounter
in a button click event handler:
const onBtnClick = () => {
refCounter.current += 1;
nonRefCounter += 1;
}
Once the application UI is rendered, it is best to ensure that interactions with ref
object values are done in response to events that occur in the application or when processing side effects.
Summary
React useRef
is a hook that allows a function component to create a reference to a value and track it between component re-renders. It can also be used to reference DOM elements and retrieve their values or manipulate them.
Updates to ref objects to do trigger a re-render of the component. Hence, they are typically used for storing values that are not expected to be visible in the application UI. They are used internally to keep reference to values.
Since useRef
is a hook, its usage must comply with the Rules of Hooks in React. Notably, useRef
has to be used in the top level of the function component.