When you don't need to use useState in React

React's useState hook is an integral part of functional component-based state management in React. It allows you to add state functionality to functional components, increasing the utility and flexibility of these components. However, while useState is commonly used in many React applications, there are scenarios where useState is not necessary and alternative methods can be used instead.
In this article, we will discuss a few scenarios where you can avoid using useState, and demonstrate these alternatives with code snippets.
Let's start with a basic useState example and see which options we have.

import React, { useState } from "react";

export default function App() {
  const [email, setEmail] = useState("");

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

    alert(`Submitting email ${email}`);
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          Email:
          <input
            type="email"
            name="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

Using useRef instead of useState

One such scenario is when you do not need to re-render a component, but only need to maintain a mutable value. In this case you can use the useRef hook instead of useState. Let's have a look at an example:

import React, { useRef } from "react";

export default function App() {
  const emailInputRef = useRef();

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

    alert(`Submitting email ${emailInputRef.current.value}`);
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          Email:
          <input type="email" name="email" ref={emailInputRef} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

In this example, the useRef hook is used to hold a reference to the email input field. This value is then accessed directly when the form submission is handled. No re-rendering is triggered even if the value of the email input field changes.

Using the FormData pattern instead of useState

Another scenario where useState can be avoided is when dealing with forms. Normally, you could use useState to track the value of each form control. But there's an alternative: the FormData pattern, which uses useRef and the form data.

import React, { useRef } from "react";

export default function App() {
  const formRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(formRef.current);
    alert(`Submitting email ${formData.get("email")}`);
  };

  return (
    <div className="App">
      <form ref={formRef} onSubmit={handleSubmit}>
        <label>
          Email:
          <input type="email" name="email" />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

In this example, we use useRef to keep track of the entire form. Then, when the form is submitted, we use the browser's native FormData API to retrieve the data from the form.

Using useSearchParams instead of useState

The useSearchParams hook from the react-router-dom library may be a better option than useState for dealing with the search parameters of a URL. This hook returns getter and setter methods for the search parameters in a React router's URL. See the example below:

import React from "react";
import { useSearchParams } from "react-router-dom";

export default function App() {
  const [searchParam, setSearchParam] = useSearchParams();
  const handleSubmit = (e) => {
    setSearchParam({ search: e.target.search.value });
    alert("search: " + e.target.search.value);
    e.preventDefault();
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        <label>
          search:
          <input name="search" value={searchParam.get("search")} />
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

In this snippet, instead of using useState to handle the state of the search input, we use the useSearchParams hook. This hook automatically synchronises with the URL query parameters, keeping the state synchronised with the URL. The advantage of this approach is that when a user shares the link to your application, fields are pre-populated based on that URL.

In summary

While useState is an incredibly useful hook in React, there are scenarios where other hooks might be a more efficient alternative. Whether it's avoiding unnecessary renders with useRef, handling form data with the FormData pattern, or working with URL parameters with useSearchParams, alternative methods can often provide simpler and more performant solutions. Remember, the best tool for the job depends on the specific requirements and constraints of your problem.

Happy coding!