DEV Community

Hassani
Hassani

Posted on

React useState vs useRef

React’s useState and useRef hooks are essential tools for managing data within functional components. If like me, you are new to react, it will save you a lot of time and heartache to learn the difference between these react hooks and when to use them. In this post we will dive into the functional differences between these components and when is best to use them.

Managing Dynamic state: useState

The useState hook, as the name suggests, is used to keep the state of your react functional component. The useState Hook in React is the equivalent of this.state/this.setSate found in React class components. You should use it when you want to store a value that changes over time and update the UI whenever that value changes.

How to use it?

To use the useState hook, we first need to import it into our component.

import React, { useState } from 'react';
Enter fullscreen mode Exit fullscreen mode

We initialize our state by calling useState in our function component. useState accepts an initial state and returns two values

  • The current state.
  • A function that updates the state.
const [name, setName] = useState('');
Enter fullscreen mode Exit fullscreen mode

The first value, name, is our current state. The second value, setName, is the function that is used to update our state.

When to Use useState

  • Form handling: Track input values and validation messages.
  • Toggles: Show/hide elements like modals, dropdowns, and alerts.
  • API responses: Store and display data fetched from an API.
  • Counters: Anything that tracks numbers and needs to be displayed on the screen (e.g., likes, steps, score)

Example

In this example, useState keeps track of the email and password inputs. Each time the user types, the state updates and the form reflects the new values.

import React, { useState } from 'react';

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

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Email: ${email}\nPassword: ${password}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="email" 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
        placeholder="Enter your email" 
      />
      <input 
        type="password" 
        value={password} 
        onChange={(e) => setPassword(e.target.value)} 
        placeholder="Enter your password" 
      />
      <button type="submit">Sign Up</button>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode

Important note

When you call reacts useState hook you may notice something strange. The update is not immediately applied to the state. So if you log a value immediately after calling its set function you will see that that value is unchanged. The example below shows this exact issue

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log('Before setCount:', count);
    setCount(count + 1);
    console.log('After setCount:', count); // This will log the old value, not the updated one
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

If you click on the Increment button, you will see that the Before setCount: The current state (count) is 0. After setCount: The state hasn’t updated yet, so it still logs 0. The new state (1) will only be reflected on the next render. This is because when you call setState, it schedules a state update but React may delay applying the update for performance reasons, especially in function components with concurrent rendering.

How to ensure you get the latest state
If you need to update state based on the previous state, always use the functional form of setState:

setCount((prevCount) => prevCount + 1);
Enter fullscreen mode Exit fullscreen mode

Keeping Persistent References: useRef

The useRef hook lets you create a persistent reference to a value or a DOM element. Unlike useState, changes to a useRef value don’t trigger a re-render. This makes it perfect for scenarios where you need to keep track of something without affecting the UI.

How to use it?

To use the useRef hook, we first need to import it into our component.

import { useRef } from "react";
Enter fullscreen mode Exit fullscreen mode

The hook only returns one item, an Object called current which we can use to change the variable.

const count = useRef(0);

useEffect(() => {
    count.current = count.current + 1;
  });
Enter fullscreen mode Exit fullscreen mode

When to Use useRef

  • Access and manipulate DOM elements (e.g., focus an input).
  • Store mutable values across renders (e.g., tracking a timer).
  • Track previous state values for comparisons.

Example

In this example, useRef is used to store the timer ID. Unlike useState, updating timerRef.current won’t cause the component to re-render, making it a great place to keep this kind of information.

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

function Timer() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (!timerRef.current) {
      timerRef.current = setInterval(() => setTime((t) => t + 1), 1000);
    }
  };

  const stopTimer = () => {
    clearInterval(timerRef.current);
    timerRef.current = null;
  };

  return (
    <div>
      <h1>Time: {time}s</h1>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Final thoughts

If you are like me you tend to stick to something you know and use it as much as possible, This can work but knowing what to use when will lead to much cleaner and performant code. Which is something we should all strive for.
Both useState and useRef are essential hooks in React, but they serve very different purposes:

  • Use useState when your data should cause the UI to update. Remember, it’s not synchronous, so use the functional form when updating the same state multiple times in a row.
  • Use useRef for anything that needs to persist without causing a re-render—like accessing a DOM element, storing timers, or tracking how many times a button has been clicked. Learning this differentiation has certainly made my life easier and my work better and helps me understand the flow of code in react. Try them out in your own projects and see how they can simplify your code!

Have questions or found any issues with my blog? Let’s chat in the comments!

Top comments (0)