Menu Close

Understanding React useRef Hook


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.

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:

JavaScript
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:

JavaScript
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.

JavaScript
// 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:

JavaScript
import { useRef } from 'react';

Once imported, we can create a reference variable by calling useRef():

JavaScript
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:

JavaScript
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():

JavaScript
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():

JavaScript
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:

JavaScript
// 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:

JavaScript
{ 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:

JavaScript
// 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:

JavaScript
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.

JavaScript
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.

JavaScript
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:

HTML
<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:

JavaScript
// 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.

JavaScript
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:

JavaScript
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:

JavaScript
// 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:

JavaScript
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.

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
<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, then useRef 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, using useRef to retrieve the values can be very efficient. If useState 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 with useState 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:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
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.


Leave a Reply

Your email address will not be published. Required fields are marked *