State Management in React: Exploring useRef and useState

State Management in React: Exploring useRef and useState

Harnessing the Power of useRef and useState in React: Exploring Differences, Similarities, and Combined Use

React is a powerful JavaScript library for building user interfaces, and it provides several tools and hooks for managing state and interacting with the DOM.
Two commonly used hooks in React are useRef and useState.
While both are essential for various use cases, they have different purposes and behaviours.
In this article, we'll explore the differences and similarities between useRef and useState, along with code samples to illustrate their usage.

useRef Hook

The useRef hook in React allows you to reference a value that is not needed for rendering. It is typically used for:

  • Referencing a DOM element.

  • Storing information between re-renders.

  • Managing data that does not impact the visual output of a component.

Basic Usage of useRef

import React, { useRef } from 'react';

function MyComponent() {
  const intervalRef = useRef(0);
  const inputRef = useRef(null);

  // ...

  return (
    // ...
  );
}

In the code above, we create two useRef instances: intervalRef and inputRef.

  • intervalRef is used to store information (an interval ID) between renders.

  • inputRef is used to reference a DOM element (in this case, an <input>).

Key Points about useRef

  1. Changing a useRef Does Not Trigger Re-render: Unlike state variables, changing a value useRef does not cause a component to re-render.
    This makes it suitable for storing information that should not affect the component's visual output.

  2. Do Not Write or Read ref.current During Rendering: Writing to or reading from ref.current during the rendering phase can lead to unexpected behaviour.
    It's best to interact with ref.current in event handlers or effects.

Example: Click Counter Using useRef

In the following example, we use a useRef to keep track of how many times a button is clicked:

import React, { useRef } from 'react';

function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('You clicked ' + ref.current + ' times!');
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}

This example demonstrates how useRef can be used to store information between re-renders without triggering re-rendering.

useState Hook

The useState hook in React is used to manage component-level state.
It is typically employed for:

  • Managing data that affects the visual output of a component.

  • Triggering re-renders when state changes.

Basic Usage of useState

import React, { useState } from 'react';

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

  // ...

  return (
    // ...
  );
}

In this example, we use the useState hook to create a state variable count and a function setCount to update its value.
Changing count triggers a re-render of the component.

Key Points about useState

  1. State Variables Trigger Re-renders: Changing a state variable, such as count in the above example, triggers a re-render of the component.
    This is because state variables affect the visual output of the component.

  2. Best for Displayed Data: useState is best suited for managing data that should be displayed on the screen, as it automatically updates the UI when the state changes.

Example: Click Counter Using useState

In this example, we use the useState hook to create a simple click counter:

import React, { useState } from 'react';

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

  function handleClick() {
    setCount(count + 1);
  }

  return (
    <div>
      <button onClick={handleClick}>
        Click me!
      </button>
      <p>You clicked {count} times!</p>
    </div>
  );
}

Here, we use useState to manage the count, and each click on the button updates the displayed count.

Differences and Similarities

Differences

  1. Purpose: useRef is primarily used for referencing values that do not affect rendering, such as DOM elements or non-displayed data. useState, on the other hand, is used for managing state variables that do affect rendering.

  2. Re-rendering: Changing a useRef value does not trigger a re-render while changing a useState value does.

Similarities

  1. Both are Hooks: Both useRef and useState are hooks provided by React and can be used in functional components.

  2. Immutable Updates: When updating values, it's a good practice to create a new reference or state value.
    For useRef, this is achieved by changing the current property.
    For useState, you use the updater function or the spread operator to create a new state object.

  3. Initialization: Both hooks allow you to provide an initial value when creating the reference or state variable.

  4. Local to Component: Values stored in useRef or state variables are local to the component that defines them, making them suitable for component-specific data.

Choosing Between useRef and useState

When deciding whether to use useRef or useState, consider the following guidelines:

  • Use useRef for non-render-impacting data or DOM manipulation.

  • Use useState for managing the state that impacts the visual output of the component.

In practice, these hooks can often complement each other within the same component, with useState handling displayed data and useRef managing auxiliary information or DOM elements.

Using useRef and useState Together

In React, there are scenarios where you need to combine the power of both useRef and useState to create efficient and dynamic components.
We can explore this combination with an example of a stopwatch.

Example: Stopwatch Using useRef and useState

Suppose you want to build a stopwatch that keeps track of elapsed time while allowing users to start and stop it.
Here, you can use useState to manage the running state and the displayed time, while useRef comes in handy for storing the interval ID, ensuring it doesn't trigger re-renders.

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

function Stopwatch() {
  const [isRunning, setIsRunning] = useState(false);
  const [time, setTime] = useState(0);
  const intervalRef = useRef(null);

  const startStop = () => {
    if (isRunning) {
      clearInterval(intervalRef.current);
    } else {
      intervalRef.current = setInterval(() => {
        setTime((prevTime) => prevTime + 1);
      }, 1000);
    }
    setIsRunning(!isRunning);
  };

  const reset = () => {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
    setTime(0);
    setIsRunning(false);
  };

  return (
    <div>
      <h1>Stopwatch</h1>
      <p>Time: {time} seconds</p>
      <button onClick={startStop}>{isRunning ? 'Stop' : 'Start'}</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

In this example, useState is used to manage the running state (isRunning) and the displayed time (time). On the other hand, useRef is used to store the interval ID (intervalRef) for managing the timer logic.
This combination allows us to create a dynamic stopwatch that efficiently updates the time without causing unnecessary re-renders.

When to Use the Combined Approach

  • Use useState for managing state variables that affect the visual output of your component.

  • Use useRef for referencing values that are not needed for rendering or for storing non-render-impacting data.

  • Combine both hooks when you need to maintain non-render-impacting data and state that affects the visual output within the same component.

The combined approach allows you to create versatile React components, such as the stopwatch, that efficiently manage different aspects of their behaviour while adhering to best practices in React development.
By leveraging both useRef and useState, you can build dynamic and interactive components that are both performant and maintainable.

In conclusion, both useRef and useState are essential tools in a React developer's toolkit. Understanding their differences and similarities and knowing when to use each or both can help you build more efficient and maintainable React components.