useRef Hooks text

React UseRef: Create Scalable Apps That Perform Like a Dream

October 14, 2022
Victor Yakubu
React Best Practices

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

As you grow in your journey as a React developer, you will begin to focus more on scalable and performance. And to achieve better performance in your application you will need to start using advanced React hooks like React useRef.

This article is aimed at giving you a solid understanding of how the React useRef hook works, and some use cases of the hook. We will also be considering the difference between useRef and useState, we will also touch on createRef and forwardRef.

Requirements

I strongly recommend you follow along as we treat examples. and to do that, there are certain things you should have

What are React Hooks?

Having an understanding of what Hooks are and the value they bring, will make you appreciate useRef and other react hooks and custom hooks better.

Hooks are features that were introduced to React 16.8, it allows you to use state and other life cycle methods within a function-based react component. It allows us to break out functionality into reusable functions that we can then use in our components.

React useref hook

React useRef and useState hooks are similar in the sense that it persists data between renders of your component. What does this mean? It simply means that they both store/hold the state values of a component even after it is re-rendered.

So if they are both used to store data between renders, what is then the difference between them?

How is useState different from the useRef hook?

React useRef persists(store) values between renders BUT it does not re-render the react component by any chance. Even when the initial value stored in a useRef is updated, useRef will not render the component and if peradventure another function causes the component to re-render, the reference value is persisted(stored). However, in the case of useState, any change in persisted data will cause component re-renders.

useRef and useState hook also have different syntaxes:

const reference = useRef(initialValue);

The useRef hook is mutable, it returns a mutable ref object, so initialValue can be updated without it affecting the React lifecycle.

const [value, setValue] = useState();

The useState hook, on the other hand, is a reactive hook, it returns the value and a setValue function that is then used to update the state, the actions cause changes in the React lifecycle and thereby cause re-renders.

To further drive home these points, let’s consider a simple example.

Example: Build a basic React application that shows the number of times a component renders on the screen.

First Approach Using useState

How do you go about implementing this? Let’s use the useState hook first to see how it works with the following code.

import React, {useState, useEffect} from 'react'

function App() {
  const [name, setName] = useState("")
  const [countRender, setCountRender] = useState(0)

  useEffect(() => {
    setCountRender(prevCount => prevCount + 1)
  })
  return (
    <div>
      <input type="text" value={name} 
       onChange={(e) => setName(e.target.value)} 
      />
      <div>{name}</div>
      <div>This component has rendered {countRender} times</div>
    </div>
  )
}
export default App

Using the above example, what you will get is an infinite loop, because everything time the countRender value state changes, it causes the component to render. The value of countRender will continue to increase thereby throwing an infinite loop. This is what I got when I allowed the code to run for a few seconds before stopping it

So, useState is not the way to handle this problem because it will cause you to get into this infinite loop. You can read about why and how to use the useState hook here.

Second Approach Using useRef hook

Since we couldn’t solve the problem using useState, can useRef solve the problem? lets see

import React, {useState, useEffect, useRef} from 'react'

function App() {
  const [name, setName] = useState("")
  const countRender = useRef(0)

  useEffect(() => {
    countRender.current = countRender.current + 1
  })
  
  return (
      <div>
       <input type="text" value={name} 
       onChange={(e) => setName(e.target.value)} 
      />
      <div>I typed {name}</div>
      <div>This component has rendered {countRender.current} times</div>
    </div>
  )
}
export default App
useRef vs useState

Yes, therefore, useRef is the solution to this problem because it does not cause your component re-renders when the value is updated. So we can change countRender as many times as we want and it will not cause a re-render of the component because it is completely separate from our component render cycle.

Also while a useRef returns a mutable ref object that does not affect the React lifecycle, useState returns a value and a setValue function and it doesn’t directly mutate the returned value, however it used the setValue function to mutate the value which provokes changes in the react lifecycle.

Common Usecase for userRef hook

There are a few different scenarios where you will use useRef, but for this article, we will be considering three use cases:

Accessing dom elements

This is one of the most popular use cases for useRef. Each element inside your document has a ref attribute and you can set it to anything you want, you can either get the value of an element, change some of its styles and so on.

Earlier, I started that userRef returned object, well don’t just take my word for it, see it for yourself. From the code below, we will log inputRef to the console when the submit button is clicked.

import { useRef } from 'react'

function App() {
  const inputRef = useRef()

  const onSubmit = (e) => {
    e.preventDefault()
    console.log(inputRef)
  }

  return (
    <div>
      <form onSubmit={onSubmit}>
        <label htmlFor='name'>Name</label>
        <input
          type='text'
          ref={inputRef}
          id='name'
        />
        <button type='submit'>
          Submit
        </button>
      </form>
    </div>
  )
}

export default App

Let’s see what we will get on our browser log.

useRef returning an object

So we get an object with one property called current which is the actual DOM element which we can get by replacing console.log(inputRef) with console.log(inputRef.current)

useRef DOM element

We can also see the value of the input field by simply using console.log(inputRef.current.value).

Having access to the DOM elements and their value with useRef means that we can do anything we want with it. Like the following code below, we can set a default input value, change the background colour of the input element and also put focus on the input field.

import { useRef } from 'react'

function UseRefExample1() {
  const inputRef = useRef()

  const onSubmit = (e) => {
    e.preventDefault()
    console.log(inputRef.current.value)
    inputRef.current.value = 'Hello'
    inputRef.current.style.backgroundColor = 'green'
    inputRef.current.focus()
  }

  return (
    <div>
      <form onSubmit={onSubmit}>
        <label htmlFor='name'>Name</label>
        <input type='text' ref={inputRef}
        />
        <button type='submit'> Submit </button>
      </form>
    </div>
  )
}

export default App

Here is how it will render on the browser

useRef focus()

Using useRef to store the previous value of our state

This is also another common use of the useRef hook, to store previous values.

From the code below, we want to be able to get the previous value typed into the input field even as we type.

import React, { useState, useEffect, useRef} from 'react';

export default function App() {
    const [name, setName] = useState("");
    const prevName = useRef("");

    useEffect(() => {
        prevName.current = name;
    }, [name]);

    return (
      <>
        <input
          value={name}
          className="form-control mb-2"
          onChange={(e) => setName(e.target.value)}
        />
        <div className="btn ">Current name state: {name}</div> <br/> <br />
        <div className="btn">
          Previous name state: {prevName.current}
        </div>
      </>
    );
}

Here is how the above example will work on the browser, you will notice that while the current name state is up to date with the value in the input filed, the previous name value is behind by one character.

Fixing memory leak error

The useRef hook can also be used to handle memory leaks. Before we proceed,

What is A memory leak, and why should we bother about it?

A memory leak occurs when temporary storage such as an array, object or string are allowed to grow such that they take up valuable memory on the heap resulting in the programming crashing. This is problematic for programmes that are continuously run.

Memory leak in React

In React, a memory leak will occur when we don’t cancel an event or request when a component is mounted before that component is unmounted. i.e if the state is updated after the component that required that state has been unmounted. Let’s describe a typical scenario:

When a user visits a site that makes a fetch request to the server but before the data is returned to the client-side, he clicks on a button different button that will take him to a different section of the same application.

So let’s simulate a similar scenario and try to fix it using the useRef hook. We will make a fetch request and then unmount the component before handling the response, and see if we will have a memory leak.

So there are a few steps we need to undergo here, they’re simply steps so no need to worry.

  • So we will create a new file to house a component that we will be able to unmount before it finishes running. Then, we’ll see what error we will be getting on the console.
  • Let’s call the new component Post.jsx, we will be using the jsonplaceholder to simulate a fetch request.

Code Implementation

import { useState, useEffect } from 'react'

function Post() {
  const [loading, setLoading] = useState(true)
  const [post, setPost] = useState({})


  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then((res) => res.json())
      .then((data) => {
        setTimeout(() => {
            setPost(data)
            setLoading(false)
        }, 3000)
      })
  }, [])

  return loading ? <h3>Loading...</h3> : <h1>{post.title}</h1>
}

export default Post
  • From the code above, we used a setTimeout to cause a delay. This is so that we can have time to unmount the component before the fetch request is completed.
  • In our App.js we will import the Post.jsx component and implement a conditional rendering.
import { useState } from 'react'
import Post from './Post'

function UseRefExample3() {
  const [showPost, setShowPost] = useState(true)

  return (
    <div>
      {showPost && <Post />}
      <button
        className='btn btn-primary'
        onClick={() => setShowPost(!showPost)}
      >
        Toggle Post
      </button>
    </div>
  )
}

export default App
  • From the code above, we want to be able to unmount the Post component when we click the Toggle Post button.
  • When we ran the code, This is what you will have on the browser. Before the post load, we will see a loading screen, that will delay for 3 seconds just as we wanted.loading screen
  • Before the post loads, click on the button to unmount the component. It will cause a memory leak, you can see the error in your console.memory leak error

So the question now is, how do we handle this memory leak using the useRef hook?

  • First thing first, in the Post.jsx, bring in useRef and create a reference called isMounted.
  • In the useEffect, outside the fetch, we will return a function that runs when the component is unmounted.
  • Lastly, we will wrap setPost and setLoading in isMounted.current and then add isMounted as a dependency in our useEffect.
import { useState, useEffect, useRef } from 'react'

function Post() {
  const [loading, setLoading] = useState(true)
  const [post, setPost] = useState({})

  const isMounted = useRef(true)

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then((res) => res.json())
      .then((data) => {
        setTimeout(() => {
          if (isMounted.current) {
            setPost(data)
            setLoading(false)
          }
        }, 3000)
      })

    // Runs when the component is unmounted
    return () => {
      isMounted.current = false
    }
  }, [isMounted])

  return loading ? <h3>Loading...</h3> : <h1>{post.title}</h1>
}

export default Post

Now, when we run the application on the browser and unmount the component before the fetch request is completed, we won’t get a memory leak. If you have been following along you can confirm this by checking your browser console.

Some Frequently Asked Questions about the userRef hook

Before we conclude, there are some common questions that people often ask. All questions relate to the useRef hook, and it would be nice to answer them. Some of them include:

What is the difference between useRef and createRef?

Here is a table, stating these differences:

If you don’t know the difference between functional and class components, here is an article on the A-Z of React components.

When should I use useRef and createRef in my React Application?

Can we consider a quick example? Okay, one of the differences we mentioned is that createRef will always create a new ref while useRef uses the same ref throughout, so let’s see how.

Using createRef
import React, { useState, useEffect, createRef } from 'react';

export default function App() {
  const [count, setCount] = useState(0);
  const ref = createRef(null);

  useEffect(() => {
    ref.current = 'Victor Yakubu';
  }, []);

  useEffect(() => {
    console.log('ref', count, ref.current);
  }, [count]);
  return (
    <>
      <h1>{count}</h1>
      <button type="button" onClick={() => setCount(count + 1)}>
        Increase count
      </button>
    </>
  );
}

From the code above, we want to see if createRef creates a new ref every time, so the first useEffect will run only once, setting the initial ref, while the second useEffect will run whenever the count changes. let’s see the output on the browser and what happens on the console when we click the increase count button

From our output, we can see that our statement is valid, createRef creates a new ref after every render.

Using useRef hook

Let’s do the same thing, but this time using useRef instead.

import React, { useState, useEffect, useRef } from 'react';

export default function App() {
  const [count, setCount] = useState(0);
  const ref = useRef(null);

  useEffect(() => {
    ref.current = 'Victor Yakubu';
  }, []);

  useEffect(() => {
    console.log('ref', count, ref.current);
  }, [count]);
  return (
    <>
      <h1>{count}</h1>
      <button type="button" onClick={() => setCount(count + 1)}>
        Increase count
      </button>
    </>
  );
}

Here is what we will get on the console. We can also confirm that useRef use the same ref throughout. Which means our statement is valid.

What is the difference between useRef and forwardRef?

forwardRef or forwarding ref is a technique that is used to pass ref from a parent component to its child. So in cases where you want an action in a parent component to cause a response in its child component, forwardRef does the job.

Key Points about useRef

In summary, these are some key points to note about the React useRef hook

  • Updating the reference value in useRef will not cause the component re-renders.
  • useRef returns an object with a property called ‘current
  • In useRef, the reference value is persisted between renders.

Recommended Resources

Through the course of this article, we have been able to answer common questions about useRef and also provide a great foundation on how React useRef works. However, if you still want to further solidify your knowledge of React useRef, I’d recommend the materials below for you to go through:

Conclusion

Knowing when and how to use React hooks is valuable for a React developer, as it will greatly increase the performance of your application.

In this article, we talked about the difference between useState and useRef with an example, we also explained three use cases for useRef, how we can use useRef to access DOM elements, store previous states and also handle memory leaks.

To wrap things up, we asked and answered some common questions; like the difference between createRef, forwardRef with useRef hook. I hope you find this article useful in understanding the useRef hook. You can head to our blog to read more amazing articles.

Finally, before you go. Do you know that you can convert Figma designs to React components in a few minutes with the Copycat plugin, thereby saving you and your team lots on web development? Amazing right? check it out now.

Related Articles

  • React Best Practices

    How to Present Designs to Developers: Figma Developer Handoff Guide

    Introduction Most Applications we use today go through a User Interface (UI) design stage. This is when the product designers sketch screens and components the users are going to interact with, in the application. The Product Designers also demonstrate the…

    December 24, 2022
  • React Best Practices

    How to Virtually Render Large Lists Using React Virtualized

    All web applications display lists and tabular data in one way or another. You have probably done it hundreds of times. But what if you need to show a large number of items in a row at the same time?…

    November 29, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code