useState vs useRef: Which React Hook to Use When

Explore what these React hooks are, their functions, differences, and dive into specific use cases with easy-to-understand examples.

If you’re diving into the world of web development with React, you’ve likely come across terms like useState and useRef. These two hooks play crucial roles in building interactive and dynamic components.

Here, we’ll explore what they are, their functions, differences, and dive into specific use cases with easy-to-understand examples.

React hooks

useState: Reactively Managing State

useState is like the MVP (Most Valuable Player) when it comes to managing state in a functional React components. It let’s you add state management capabilities making components reactive and visually updatable

Let’s see this small code:

import React, { useState } from 'react';

function Counter() {
  //Declare state variable 'count' and function to update it 'setCount'
  const [count, setCount] = useState(0);

  const increment = () => {
    //Update the 'count' state using the 'setCount' function
    setCount(count + 1);
  };

  //Render a div containing the current count and a button to increment
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
} 

Here, we declare a state variable count using useState(0). Clicking the “increment” button updates the state, triggering a re-render.

And what happens when values are re-rendered?

On a re-render, the state retains its value, staying consistent between renders. React efficiently compares current and previous states to make necessary updates.

useRef: Persistence without Re-rendering

Unlike useState, useRef doesn’t trigger re-renders when its value changes. It’s handy for maintaining persistent references to DOM elements or storing values without an immediate visual reaction.

Let’s illustrate with an example focusing on an input:

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

function AutoFocusInput() {
  //Create a ref to store the input element
  const inputRef = useRef();

  //useEffect is used for side effects in functional components
  useEffect(() => {
    //focus on the input element when the component mounts
    //Empty dependency array ensures it runs only once
    inputRef.current.focus();
  }, []);
  
  //Render an input element with the ref
  return <input ref={inputRef} />;
}

In this example, useRef creates inputRef, a reference persisting between renders. Even if the component is re-rendered, inputRef stays unchanged.

And what happens when values are re-rendered?

In the example above, the inputRef remains unaffected during re-renders, ensuring stability even if the component undergoes changes.

And when do I choose useRef over a regular variable?

Wondering why use useRef instead of a regular let variable? The magic lies in useRef’s unique properties that set it apart:

  • Persistence between renders: Unlike a let variable recreated on each render, useRef maintains its value between renders, ensuring persistence.
  • No unnecessary re-renders: Modifying a let variable, even without visual changes, might trigger unnecessary re-renders. useRef avoids this by not causing re-renders.

Choosing useRef ensures a more efficient approach in situations where maintaining values between renders is essential, and avoiding unnecessary re-renders is a priority.

forwardRef: Sharing Refs Between Components:**

Sometimes, you need to pass a ref from a parent component to a child. Enter forwardRef, allowing refs to be passed to child components using useRef.

Let’s see an example:

import React, { forwardRef, useRef, useImperativeHandle } from 'react';

//Child Component
const ChildComponent = forwardRef((props, ref) => {
  //Declare a internal ref variable for the input element
  const internalRef = useRef();

  //Use useImperativeHandle to expose the focus function to the parent component
  useImperativeHandle(ref, () => ({
    focus: () => {
      //Call the focus function on the internal ref
      internalRef.current.focus();
    }
  }));

  //Render an input element with the internal ref
  return <input ref={internalRef} />;
});

//Parent Component
function ParentComponent() {
  //Create a ref in the parent component
  const childRef = useRef();

  //Function in the parent component that triggers the focus function
  const handleButtonClick = () => {
    childRef.current.focus();
  };

  return (
    <div>
      {/* Render the Child Component with the ref */}
      <ChildComponent ref={childRef} />

      {/* Button that trigger the focus function in the Child Component */}
      <button onClick={handleButtonClick}>Focus on Input</button>
    </div>
  );
}

In this example, forwardRef passes a ref from the parent (ParentComponent) to the child (ChildComponent), allowing direct access to the input’s focus function.

Key Differences and Choosing the Right One

  • useState: Perfect for managing states triggering component re-renders. Great for interactive and dynamic components where state changes demand visual updates.
  • useRef: Handy for maintaining persistent references to DOM elements or storing values without causing re-renders. Ideal for direct DOM manipulations or when re-rendering isn’t desired.

Remember, you can use both hooks in the same component to address different needs. Understanding when and how to apply each is crucial for effective React development.

In a nutshell…

If you want to update data and cause UI updates, go with useState. If you need data changes throughout the component’s lifecycle without triggering unnecessary renders, the useRef is your go-to solution.

I hope this post has shed light on the differences between useState and useRef in React development. Try these concepts in your projects and discover how they can enhance your development skills.

If you have any questions, suggestions, or corrections, please leave them in the comments.

And if you want to see more details and examples of React Hooks, check out the official React Documentation.

Continue Learning

Discover more articles on similar topics