Demystifying useRef and useMemo in React

Demystifying useRef and useMemo in React

React Hooks

When it comes to React, the commonly used hooks which are useState, useEffect and useReducer, are easy to understand as well as explain.

In this blog we'll take a look at two other mysterious hooks and overcome the challenge of understanding them!

useRef

useRef is short for reference and is a hook which allows persisting data across renders, but does so without causing the component itself to rerender. It is used by invoking useRef function and passing an initial value to it. First let's look at the syntax and how to use the hook.

const reference = useRef("initial value")

This returns an object which has a key called current, initialized with this initial value.

{
  current: 'initial value'
}

A very common use case for using useRef is for when, suppose you click on a button, and then on its click you want an input to come into focus. To do this, we would need to access the DOM element of input and then call its function focus() to focus the input. This is easy to do in JavaScript by just selecting the input using querySelector or by id/class and then calling its focus() function, but React does not have a simple way built in to do it. So this can be achieved using useRef.

function focusOnInput() {
  const inputToFocus = useRef(null);
  const clickHandler = () => {
    inputToFocus.current.focus();
  };
  return (
    <>
      <input ref={inputToFocus} type="text" />
      <button onClick={clickHandler}>Focus on Input</button>
    </>
  );
}

Currently this means that the inputToFocus would look like this:-

{
  current: input
}

Every element has the ref property to access it in this way. Now let's try to understand what it means to want the previous value to be preserved across renders.

Persist data using useRef

Every functional component in React is re rendered whenever state values change. It is a JS function whose entire code is run every time a component renders. Suppose we want to persist data across these renders, the very obvious way is to use useState.

export default function App() {
  let prevValue = 0;
  const [ctr, setCtr] = useState(0);

  useEffect(() => {
    console.log("ctr:", ctr, "prevValue:", prevValue);
  }, [ctr]);

  return (
    <div className="App">
      <p>{ctr}</p>
      <button
        onClick={() => {
          setCtr((ctr) => {
            prevValue = ctr;
            return ctr + 1;
          });
        }}
      >
        Increase by 1
      </button>
    </div>
  );
}

Now ideally you might expect that the prevValue keeps updating along with the ctr. But this does not happen. But the actual output is as follows:-

useRef.gif

This is because React says, hey! anything you write as a local variable inside my functions will be lost forever on rendering! It is not my responsibility to keep track of the local variables! So whenever the ctr is increased, all local variables are reset. Now to persist these local variable values without using useState (and thus causing unnecessary re-renders), we can use useRef, as follows:

export default function App() {
  const prevValue = useRef(0);
  const [ctr, setCtr] = useState(0);

  useEffect(() => {
    console.log("ctr:", ctr, "prevValue:", prevValue);
  }, [prevValue, ctr]);

  return (
    <div className="App">
      <p>{ctr}</p>
      <button
        onClick={() => {
          setCtr((ctr) => {
            prevValue.current = ctr;
            return ctr + 1;
          });
        }}
      >
        Increase by 1
      </button>
    </div>
  );
}

The output looks as follows now, it works!

image.png

There are many use cases for useRef in React which allows us to avoid unnecessary renders, and allows access to input functions such as focus and blur. These were simple examples of the same, hope it was clear!

useMemo

Let's first understand a little thing called memoization.

Memoization

Suppose we have a function

function calculateSum(num1, num2) {
  // takes two arguments, returns a result after performing an expensive operation
  return finalValue;
}
  • Since functions are created to be reused again and again, there might be cases where the same function is being called with the same arguments. Now if this function performs a time consuming operation, it causes our code to be slow. And as we all know, time is money ;)

  • Memoization is a concept where we store the calculated value of an expensive function into the cache. Thus the computer remembers what value the function will return for specific values of arguments, and we can simply access this remembered value without actually performing the expensive calculations inside the function. This optimizes our code, making it run faster and smoother.

  • In React, there are cases where a component doesn't have to be re-rendered again and again, but because it's a child of a parent where state or props are changing, that causes it to be re-rendered.

Where to use useMemo

Let's take an example. Suppose we have a static Welcome Card to be displayed inside an application. The app also contains other states, for example a counter. Since the Welcome Card is a child of the main parent App, any state changes inside App will cause the static card to be re-rendered everytime the counter is increased.

//App.jsx
import { useState } from "react";
import WelcomeCard from "./WelcomeCard";
import "./styles.css";

const App = () => {
  const [ctr, setCtr] = useState(0);
  return (
    <div className="App">
      <h1>Application</h1>
      <button onClick={() => setCtr(ctr + 1)}>
        You clicked me {ctr} times.
      </button>
      <WelcomeCard />
    </div>
  );
};

export default App;
//WelcomeCard.jsx
import { useEffect } from "react";

const WelcomeCard = () => {
  useEffect(() => {
    console.log("Hey I am rendering again -_-");
  });

  return (
    <div className="card card-text-only card-flex">
      <h2>Hi there!</h2>
      <p>My name is Bhavya</p>
      <p>Nice to see you here</p>
    </div>
  );
};

export default WelcomeCard;

This is the output

YRhBIf46Z.png

As we can see, the WelcomeCard is rendered again everytime we click on the counter, in fact it is rendered more times than the counter. This is happening even though there is nothing that is dynamic inside the card itself, thus the re-rendering is a waste. Now imagine a website with lots of static data components, which re-render everytime states or states of parents change. To avoid this we can use useMemo as follows:-

//App.jsx with useMemo

import { useState, useMemo } from "react";
import WelcomeCard from "./WelcomeCard";
import "./styles.css";

const App = () => {
  const [ctr, setCtr] = useState(0);
  const memoizedWelcomeCard = useMemo(() => {
    return <WelcomeCard />;
  }, []);

  return (
    <div className="App">
      <h1>Application</h1>
      <button onClick={() => setCtr(ctr + 1)}>
        You clicked me {ctr} times.
      </button>
      {memoizedWelcomeCard}
    </div>
  );
};

export default App;

Screenshot (130).png

This ensures that the memoized card is only rendered at the initial page loading time, and doesn't re-render everytime the counter is clicked, no matter how high the value.

Yay! You have learned how to memoize your React code.


Conclusion and Important Notes

  • Memoization is a vast and important concept in Computer Science. You can read more about it here and here.
  • There are other methods to achieve memoization in React, which are React.memo() and useCallback.

That's all I have on this topic. Do let me know your thoughts and feedback below. Hope you learned something new through this! Feel free to reach out to me on Twitter or Linkedin. I am currently actively looking for job opportunities in Frontend Dev!