DEV Community

Cover image for When to use useRef() instead of useState()
Isabelle M.
Isabelle M.

Posted on

When to use useRef() instead of useState()

In React, hooks are a way to use state and other React features without having to generate a class component. One of the most frequently used hook is useState(), however on occasion useRef() might be a better and more efficient way to manage state.
A common use case is handling a form input field when the submit button is clicked. For example, lets look at the following code snippet:

const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = (e) => {
  e.preventDefault();
  console.log(email, password);
};

return (
  <form onSubmit={handleSubmit}>
    <input
      type="email"
      value={email}
      onChange={(e) => setEmail(e.target.value)}
    />
    <input
      type="password"
      value={password}
      onChange={(e) => setPassword(e.target.value)}
    />
    <button type="submit">Submit</button>
  </form>
);
Enter fullscreen mode Exit fullscreen mode

In the above example, we have two input fields, one for the email and one for the password. When the submit button is clicked, the values of the two input fields are logged to the console. The useState() hook is used to manage the state of the two input fields. Although, this code does what it's supposed to do, it also causes the component to re-render every time the input fields are changed.

This is due to the usage of the useState() hook, which by definition causes the component to re-render every time the state is changed. This is not a problem in this example, but in a more complex application, this can cause performance issues. To avoid this, we can use the useRef() hook instead of useState().

const email = useRef('');
const password = useRef('');

const handleSubmit = (e) => {
  e.preventDefault();
  console.log(email.current, password.current);
};

return (
  <form onSubmit={handleSubmit}>
    <input
      type="email"
      value={email.current}
      onChange={(e) => (email.current = e.target.value)}
    />
    <input
      type="password"
      value={password.current}
      onChange={(e) => (password.current = e.target.value)}
    />
    <button type="submit">Submit</button>
  </form>
);
Enter fullscreen mode Exit fullscreen mode

When to use useRef() instead of useState()

A rule of thumb is to use useState when you need to re-render the component when the state changes and useRef when you don't need to re-render the component when the state changes.
Here are some examples of when to use useRef instead of useState:

  • When you need to store a value that does not trigger a re-render when it is updated.
  • When you need to store a value that is not used in the render method.
  • When you need to store a value that persists for the lifetime of the component.

Top comments (8)

Collapse
 
dubcee93 profile image
dubcee93

Thanks for the post - I had to learn the difference between these a month or two ago to improve a contribution to an open-source project and was happy to read this quick post as a reminder of it. Have a good day!

Collapse
 
olsard profile image
olsard

Cool, thanks a lot for sharing!

Collapse
 
xoxsimba profile image
nannuflay

thanks

Collapse
 
hkulur profile image
Harish

So I gave this a try. It doesnt seem to work?

codesandbox.io/s/cocky-bartik-qlmm...

Because if there are no re-renders, the DOM wont update and I wont see the username and password change as I type?

Collapse
 
absami10 profile image
Abdul Sami

You can do it like this

import "./styles.css";
import { useRef } from "react";

export default function App() {
  const email = useRef("");
  const password = useRef("");

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log(email.current.value, password.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" ref={email} />
      <input type="password" ref={password} />
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mickeymaruf profile image
Maruf

Okay, now I understand the point! When I used a counter to observe the component's re-rendering, I realized that using useRef retains the state throughout the entire lifecycle of the component. So, every time it re-renders, it doesn't start with the count of 0 again unlike useState. Instead, it holds the previous value and continues counting where it should be. Correct me If I'm wrong!

Collapse
 
ugury profile image
Ugur Y

So, every time it re-renders, it doesn't start with the count of 0 again unlike useState.

I didn't quite understand what you said here. So how can useState initialize from 0 every time? Doesn't useState preserve its state value throughout its lifecycle?

Collapse
 
christianpaez profile image
Christian Paez

Very useful, thanks!