Understanding useState hook in React

Understanding useState hook in React

Learn how to efficiently manage state in your React application using the useState hook

Introduction

useState hook is one of the most used hooks when building React applications. Every single React app out there uses this hook. This article will teach you how to use this hook to manage states in your React projects. You will also learn the best practices when using the useState hook to manage multiple states.

What is useState?

React useState helps developers to add stateful behavior to functional components. Functional components were introduced after the introduction of React hooks in React v16.8. Now functional components have become the standard way of writing React components. This saved developers from the hell of class-based components.

The useState hook returns an array of two elements:

  • The current state

  • The function to update that state to a different value

Let's see a simple example of how to declare a state in a React component. I am assuming you have created a new React project, if not you can run the following command in your terminal.

npm create vite@latest learning-state -- --template react

We will be using the new frontend build tool called Vite to scaffold and bundle our project instead of using Create React App (CRA).

After creating your project, go to App.jsx file, clear everything, and paste the following code. You can also decide to clear all boilerplate CSS code, it doesn't matter here.

import { useState } from "react";

function App() {
  const [count, setCount] = useState(10);

  return (
    <>
      <h2>The count is : {count}</h2>
    </>
  );
}

export default App;

In the code above we have imported the useState hook from React, declared our state called count with the initial value of 10 and setCount a function that will update the value of the state count.

Notice that useState uses array destructuring to return an array of the two elements we defined earlier. Learn more about destructuring here.

Run the development server using the command:

npm run dev

This opens the app at http://localhost:5173/

Now let's create functions that will help us update our state by increasing and decreasing the count on button click.

Update your code as follows:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(10);

  const increase = () => {
    setCount(count + 1);
  };

  const decrease = () => {
    setCount(count - 1);
  };

  return (
    <>
      <h2>The count is : {count}</h2>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
    </>
  );
}

export default App;

You can also use anonymous functions for the buttons instead of declaring each function to minimize your code as follows:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(10);

  return (
    <>
      <h2>The count is : {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
    </>
  );
}

export default App;

This works fine. But it's not the recommended way to handle state (updating state directly) in React applications. It is a best practice to allow setCount function to calculate your next/new state based on the previous state. Our code should now look like this:

import { useState } from "react";

function App() {
  const [count, setCount] = useState(10);

  return (
    <>
      <h2>The count is : {count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>Increase</button>
      <button onClick={() => setCount((count) => count - 1)}>Decrease</button>
    </>
  );
}

export default App;

Handling multiple states

When dealing with forms in React applications, you will need to store the value of each form field in a state. Imagine you have three form fields: username, email and password. You will need to define three states and attach them to the respective input fields of the form. See the code below:

import { useState } from "react";

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

  const handleSubmit = (e) =>{
    e.preventDefault();
    console.log(username,email,password);
  } 
  return (
    <>
      <h2>Sign Up form</h2>
      <form onSubmit={handleSubmit}>
        <input type="text" value={username} onChange={(e)=>setUsername(e.target.value)}/>
        <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">Sign Up</button>
      </form>
    </>
  );
}

export default App;

This can be tedious and unprofessional but we can reduce this code by handling our state using an object that contains the form fields username, email and password. Also, we can change the onChange handlers to only one function to handle changes in our form fields.

Here is the updated code:

import { useState } from "react";

function App() {
  const [formState,setFormState] = useState({
    username:"",
    email:"",
    password:""
  })

  const handleChange =(e)=>{
     const { name, value } = e.target;
     setFormState({...formState, [name]:value});
  }

  const handleSubmit = (e) =>{
    e.preventDefault();
    console.log(formState);
  } 
  return (
    <>
      <h2>Sign Up form</h2>
      <form onSubmit={handleSubmit}>
        <input type="text" name="username" value={formState.username} onChange={handleChange}/>
        <input type="email" name="email" value={formState.email} onChange={handleChange}/>
        <input type="password" name="password" value={formState.password} onChange={handleChange}/>
        <button type="submit">Sign Up</button>
      </form>
    </>
  );
}

export default App;

Now that's a lot. Let me explain.

Here, we are declaring a single state as an object to hold all the form states of our form. It's like declaring an object called formState with the keys username, email and password with the values of empty strings initially. See this object:

  const formState = {
    username: "",
    email: "",
    password: "",
  };

Then we are also declaring a function setFormState that will be used to update the state as the second argument to the useState hook.

On the handleChange function, we are using setFormState to update the state by first spreading the formState like this ...formState . Using the spread operator, we grab all properties of our object before updating them. If you are not familiar with the spread operator you can learn more here.

Then am destructuring name and value properties from the e.target object like this const { name,value } = e.target . More on destructuring here.

Then we are using [name]:value to help setFormState function to know which property to update based on the name of the input field.

You choose to add value prop to the input or not. In my case am adding to show you how to grab the state for each input field from the formState.

We can now use our formState in our component, for now, we are just logging it out to the console inside the handleSubmit function when we submit the form.

The e.preventDefault(); is used to prevent the default behavior of form submissions.

Now you have a basic understanding of the useState hook in React. For more information, you can visit the React useState documentation.

Note: useState and other React hooks can only be called at the top level of your functional components or your own Hooks. You can’t call it inside loops or conditions. Otherwise, you will get an 'Invalid hook call' error.

Bonus tips

To create interactive forms in your React applications consider the following tips:

  • Style your forms and make them responsive to all screen devices/sizes.

  • Always validate your forms to prevent users from submitting inappropriate data to the server. You use form validation libraries such as React hook form or Formik.

  • Make your forms accessible, learn more about form accessibility here.

Conclusion

In large React applications, managing state with useState can be complex because you opt to prop drilling down to child components. This is where context API comes in handy to manage your global state or use a third-party library for state management such as Redux or Redux Toolkit.

If this article helps make sure to give a like and follow. You can also follow me on Twitter and GitHub.

Happy coding.