You might be using the useState hook too much in React.

You might be using the useState hook too much in React.

An insight into useRef() hook.

Introduction:

I will be taking an example of a handling form submission in react. Let's say you have a form that has two input fields, first name, and last name. Typically I have seen people using states to store these values which is fine when you need to change that state somewhere else in your component. But, suppose you only need to submit those values to your backend via a post request, when someone clicks on the submit button. In this case, using states is a bit of an overkill.

Why?

Take this example of this small form I made using the useState hook.

import React, { useState } from 'react';
import TextField from "@mui/material/TextField";
import Button from '@mui/material/Button';
import Alert from '@mui/material/Alert';
import './style.css';

export default function App() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  const [show, setShow] = useState(false);

  const handleSubmit = (e) => {
    e.preventDefault();
    setShow(true);
  };

  return (
    <div className="App">
      <h1 className="heading">Useref implementation</h1>
      <form className="form" onSubmit={handleSubmit}>
        <TextField
          id="outlined-name"
          label="First Name"
          className="form__child"
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
        />
        <TextField
          id="outlined-name"
          label="Last Name"
          className="form__child"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
          />
        <Button type="submit" variant="contained">
          Submit
        </Button>
      </form>

      {show && (
        <Alert severity="success" className="mt-30">
          {Thanks {firstName} {lastName} }
        </Alert>
      )}
    </div>
  );
}

The underlying issue:

When we use the onChange event to update the state of firstName and lastName, it is triggering a state update on every keystroke, which re-renders the component so many times. But if we think carefully, we only need the value stored inside the firstName and lastName after you click on the submit button. This is where the useRef hook can help us.

I removed the conditional Alert to show how state update is being performed at every keystroke.

2022-08-15 16-29-33.gif We can see that the component re-renders at every keystroke, which is fine if we want such implementation but we only want our values when we click the submit button.

Use of useRef hook:

You can imagine useRef as document.querySelector() equivalent in vanilla js.

const refContainer = useRef(initialValue)

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component. If you pass a ref object to React with

<div ref={refContainer} />

,React will set its .current property to the corresponding DOM node whenever that node changes.

Now using useRef inside the form we made earlier:

import React, { useRef, useState } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import Alert from '@mui/material/Alert';
import './style.css';

export default function App() {
  // const [firstName, setFirstName] = useState('');
  // const [lastName, setLastName] = useState('');
  const firstName = useRef('');
  const lastName = useRef('');

  const [show, setShow] = useState(false);

  const handleSubmit = (e) => {
    e.preventDefault();
    setShow(true);

    console.log(firstName.current.value, lastName.current.value);
  };

  return (
    <div className="App">
      <h1 className="heading">Useref implementation</h1>
      <form className="form" onSubmit={handleSubmit}>
        <TextField
          id="outlined-name"
          label="First Name"
          className="form__child"
          // value={firstName}
          // onChange={(e) => setFirstName(e.target.value)}
          inputRef={firstName}
        />
        <TextField
          id="outlined-name"
          label="Last Name"
          className="form__child"
          // value={lastName}
          // onChange={(e) => setLastName(e.target.value)}
          inputRef={lastName}
        />
        <Button type="submit" variant="contained">
          Submit
        </Button>
      </form>

      {show && (
        <Alert severity="success" className="mt-30">
          Thanks {firstName.current.value} {lastName.current.value}
          {/* Thanks {firstName} {lastName} */}
        </Alert>
      )}
    </div>
  );
}

Changes performed:

  • firstName and lastName are now ref containers instead of state variables.
  const firstName = useRef('');
  const lastName = useRef('');
  • using inputRef prop inside our TextFeild Container which internally points to ref inside Material UI. If you are using a normal input tag you can just use ref prop.
        <TextField
          id="outlined-name"
          label="First Name"
          className="form__child"
          // value={firstName}
          // onChange={(e) => setFirstName(e.target.value)}
          inputRef={firstName}
        />
  • To access the value inside our ref container we are using, refContainer.current.value
    <Alert severity="success" className="mt-30">
            Thanks {firstName.current.value} {lastName.current.value}
            {/* Thanks {firstName} {lastName} */}
    </Alert>
    

    Result:

2022-08-15 15-56-50.gif

Conclusion:

We are able to reduce unnecessary component re-rendering by using the useRef hook. Earlier at every keystroke, a set update was triggered which led to the component being re-rendered which is not the case now. Lastly, I would like to add that, this article isn't about the direct comparison between useState and useRef but more about focusing on the fact that using useState hook is not always the most efficient approach and thus useRef can be used in such cases.