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.