React useState is a hook (function) that enables us to create state variables in functional components. The initial state is passed to this function and it returns an array of two elements one being the variable with the current state value (not necessarily the initial state) and another being a setter function to update this value.
Before we go deep into what React useState hook is. We must first understand why hooks were introduced in the first place.
Stateless & Stateful Components
Let’s review what a stateless and stateful component is in React, how do we tell the difference between the two?
First, it will be essential for us to understand what a state is. What comes to your mind when you hear state?
A state is a special built-in object in React, which allows components to create and manage their data. Just like any variable declared in a function can only be managed within that function’s scope, a state can only be managed internally by its component.
Now that we’re familiar with what a state is, let’s understand the difference between a stateless component in React and a stateful component in React.
A stateless component in React is a component that has no state while a stateful component in React is a component that has and can manage its own state.
Example of a stateful React component:
class Wardrobe extends React.Component {
constructor(props) {
super(props);
this.state = { shoes: ['shoe 1', 'shoe 2', 'shoe 3'] };
}
render() {
return <h1>I have {this.state.shoes.length} shoes in my wardrobe.</h1>;
}
}
Example of a stateless React component:
class ReadingClub extends React.Component {
render() {
return <h1>I'm currently reading {this.props.book}!</h1>;
}
}
It is important to note that both a stateless component and a stateful component in React can receive and use props.
What is a hook?
Hooks were first introduced in React 16.8. And they’re in-built functions that allow React developers to use state and lifecycle methods inside functional components.
Before hooks were introduced, all functional components were considered stateless components because there was no other way to use state or react lifecycle methods in a functional component unless they were converted into class components.
There are a lot of React hooks but we’ll only be looking into the React useState hook in this article.
When should a hook be used?
Hooks are only used in functional components. They let you work with React without classes. It is also important to note that hooks don’t work in class components.
They only give functional components access to React features that were once only accessible to class components.
What is React useState hook?
React useState hook is a hook that allows you to have state variables in functional components. The useState hook returns a variable with the current state value and a function to update or change this value.
What does useState do in react?
As stated previously, the useState hook enables you to add a state to a function or functional component. The React useState hook lets you add local states to them.
This means any state created with the useState hook can only be managed within that component where the state was created just as variables created within a function can only be managed within that function’s scope.
It is also essential to know that React useState hook lets you store only a single data to a variable at a time unlike in a class component where multiple data can be stored in state at once.
Rules for using React useState hook
According to the official React documentation, There are just two rules for using the useState hook in React. What then are these rules?
The useState hook should always be used at the top level of your React functional component, before returning any JSX.
The useState hook must only be used in functional components. It’s especially useful for local component states. React hooks generally don’t work in a component that’s not a functional component.
Let’s use rule #1 in a code example to understand it better.
import { useState } from "react";
const WardRobe = () => {
// Proper way of using the useState hook
const shoes = useState("Naked wolfe");
return <h1>The name of this shoe is {shoe[0]}</h1>
}
It is important to remember that the useState hook should not be declared after returning JSX or inside the JSX just like the example below:
import { useState } from "react";
const WardRobe = () => {
// Wrong way of using the useState hook
return (
<>
{const shoe = useState("Naked wolfe");}
<h1>The name of this shoe is {shoe[0]}</h1>
</>
)
}
Let’s look at some code examples of how rule #2 shouldn’t be used so we understand how the useState hook should be properly used.
import { useState } from "react";
// Hooks are not allowed in class components
class Employee extends React.Component {
// This is wrong
const wrong = useState("");
render() {
return <h2>I am an Employee and I'm a React class!</h2>;
}
}
import { useState } from "react";
// Hooks are not allowed in class components
class Employee extends React.Component {
render() {
// This is also wrong
const wrong = useState("");
return <h2>I am an Employee and I'm a React class!</h2>;
}
}
It is wrong to use a React useState hook in a class component irrespective of where the useState hook was used whether on the same level as the render function or inside the render function as seen in the two examples above.
So how then do we know the difference between a class component and a functional component?
Class and functional components in react
Components are independent and reusable bits of code. They serve the same purpose as JavaScript functions, but work in isolation and return JSX. You can read more about the A-Z guide of React Components if you’d like to go deeper into understanding components. It will be good knowledge to compare against javascript functions.
There are two types of components in React:
Class components and
Function/Functional components
The class component was what was used in the first code example above.
Let’s go deeper into what a class component is. When creating a React class component, it is required that the component’s name must start with an uppercase letter.
The component must also include the extends React.Component
statement, which creates an inheritance to React.Component
, and gives the component access to React.Component’s
functions.
A React class component also must have a render() method, which returns JSX which gets compiled into HTML before rendering on the browser.
class Employee extends React.Component {
render() {
return <h2>Hi, I am an employee and I'm a React class component!</h2>;
}
}
It is essential to note that class components don’t work if the render method isn’t declared.
How do we Create State in a Class Component?
The constructor method is a special method for creating and initializing an object created with a class. There can only be one special method with the name “constructor” in a class. A SyntaxError will be thrown if the class contains more than one occurrence of a constructor method.
A constructor can use the super keyword to call the constructor of its superclass.
class Employee extends React.Component {
constructor() {
super();
this.state = {role: "Product Manager"};
}
render() {
return <h2>I am an Employee and I'm a React class!</h2>;
}
}
Declaring state in React with the useState hook
React useState hook is a named export in React that gives us access to the useState hook without having to import the useState hook if we have already imported React in our functional component.
A state variable can be declared in two ways with the useState hook in react. One of which is by importing it from React, which we go into later on in this article.
But for now, let’s see how we can declare it in React without importing it.
import React from "react";
const Letters = () => {
const alphabets = React.useState();
}
What data type can React useState hold?
React useState hook can hold any type of data, unlike the state in a class which is always an object. With the useState hook, the state can be any type. Let’s take a look at examples:
useState holding an array data type
import React from "react"; const Boutique = () => { const shoes = React.useState(["Nike", "Addidas", "Naked Wolfe"]) return ( <h1>I’m a boutique store</h1> ); }
useState holding an object data type
import React from "react"; const Boutique = () => { const storeCount = React.useState({ shoes: 300, pants: 500, shirts: 1000 }); return ( <h1>We have ${storeCount[0].shoes} shoes available in the store</h1> ); }
useState holding a string data type
import React from "react"; const Boutique = () => { const supplier = React.useState("Burberry") return ( <h1>We just got some supplies from ${supplier[0]}</h1> ); }
useState holding a boolean data type
import React from "react"; const Boutique = () => { const isOpen = React.useState(false); return ( <h1>We're ${isOpen[0] ? "Open" : "Closed"}</h1> ); }
useState holding a number data type
import React from "react"; const Boutique = () => { const customers = React.useState(7000); return ( <h1>We currently have ${customers[0]} </h1> ); }
How to import useState in React
Having seen how state variables can be declared without having to import the useState hook and the different data types that the useState hook accepts in React.
Let’s take a look at how the useState hook can be imported in React. Remember the useState hook is named import and all named imports must be wrapped with curly braces when being imported.
// Importing useState
import { useState } from "react";
const Letters = () => {
const alphabets = useState();
}
What does it mean to Initialise React useState?
Initializing a state simply means setting an initial value to the state, giving us an initial state. The initial value is the first value stored in a state before being mutated.
The only argument to the useState hook is the initial state. Let’s see how this initial state looks in code:
import { useState } from "react";
const BioData = () => {
const name = useState("Shaydee Coder");
}
The string argument ”Shaydee Coder”
is an initial value of the state variable name
. In React, state changes. It is because of this state change that a component rerenders and updates the user interface.
So, whenever we need to update or rerender a component, a state must be changed or updated from its initial state with a new value.
Now, this new value can no longer be referred to as the initial value because it is not the first value that was stored in that state. So the new value is not the initial state.
Don’t worry if you don’t know what updating a state is yet, we’ll get there.
But before we do, let’s see why it is essential to initialize a state or declare a state with an initial value.
Why is it Important to Initialize a State?
A better scenario to explain the importance of initializing a state would be when working with arrays and objects.
Initializing the state helps to avoid errors when performing operations on an array or object stored in the useState hook.
Imagine looping through an empty state or a state without any initial value. You’d get an error, right? I know you’re wondering why you would want to loop through an empty array.
Let’s take a look at a practical example, but first, let’s initialize our state with an array:
import { useState } from "react";
const WardRobe = () => {
// Initializing shoes state variable with an empty array
const shoes = useState([]);
}
We can loop through our shoes state variable
because we already have an array stored in it before populating the array with data and then doing something with that data. A real-life scenario for this is looping through a state variable that’s expecting some data from an endpoint.
Initializing the state with this array gives us the power to use any array methods on the variable without getting any error on the console even though the array is empty.
const WardRobe = () => {
// Initializing shoes state variable with an array
const shoes = useState([]);
Return (
{shoes.map((shoe, i) => {
return <h1>The name of this shoe is {shoe[i]}</h1>
})}
);
}
Without having an initial value of array in our shoes state variable
, we would get an uncaught type error if we try to loop through it before populating it with data.
const WardRobe = () => {
// Declaring shoes state variable without any initial value
const shoes = useState();
Return (
{shoes.map((shoe, i) => {
return <h1>The name of this shoe is {shoe[i]}</h1>
})}
);
}
//Uncaught TypeError: Cannot read properties of undefined (reading 'map')
We got the uncaught type error because it interpreted the `shoes variable state as undefined even though we declared a state.
It’s important to note that when we do not set an initial value to our state variables, it is the same as setting it to undefined.
Thus, we won’t be able to get access to all the methods available to the data types that the useState hook accepts in React.
Reading State
What does it mean to read a state, and how do we do that? To read a state simply means to get access to that value or data that is within that state or that has been stored in that state.
React useState hook doesn’t return just a variable as the previous examples imply.
It returns an array, where the first element is the state variable and the second element is a function to update the value of the variable.
import { useState } from "react";
// Functional component
const ShoeStore = () => {
// Stored state
const isStoreOpen = useState(false); // returns array
// stateVariable = isStoreOpen = isStoreOpen[0]
// stateFunction = isStoreOpen = isStoreOpen[1]
return (
<>
...
</>
)
}
So when reading the state the first element which is the state variable is what is used to read what values reside inside the state variable.
This is not true for a class component. The state of a class component is always an object.
Let’s see that in some code examples comparing how a state can be read in a functional component with a class component.
import { useState } from "react";
// Functional component
const WardRobe = () => {
// Stored state
const shoe = useState("Naked wolfe");
// Reading/Accessing the stored state from our JSX
return (
<>
<h1>The name of this shoe is {shoe[0]}</h1>
</>
)
}
// Class component
class WardRobe extends React.Component {
constructor() {
super();
// Stored state
this.state = {shoe: "Naked wolfe"};
}
// Reading/Accessing the stored state from our JSX
render() {
return <h1>The name of this shoe is {this.state.shoe}</h1>;
}
}
As you can see in the two code snippets above, a functional component doesn’t require the this
keyword to read its state unlike in a class component.
Updating State
Having seen how a state can be read in both a functional and class component, let’s take a look at why we need to update them and how this update can be done when using the useState hook in React.
Updating a state means changing the initial value that was stored in the state using the second element that every useState hook returns as seen previously. It is the only way to update or change the user interface of an application.
Let’s see how a state is updated in code:
import { useState } from "react";
// Functional component
const ShoeStore = () => {
// Stored state
const isStoreOpen = useState(false); // returns array
const storeStatus = isStoreOpen[0]
const setStoreStatus = isStoreOpen[1]
// Initial state result before update
console.log(storeStatus) // false
return (
<>
…
</>
);
}
When the state variable was logged to the console as seen in the above code snippet, we get false
as the result because it was the value with which the state was initialized.
Now let’s update the state and then log the result to the console to confirm if truly the state’s values changed.
import { useState } from "react";
// Functional component
const ShoeStore = () => {
// Stored state
const isStoreOpen = useState(false); // returns array
const storeStatus = isStoreOpen[0];
const setStoreStatus = isStoreOpen[1];
// Initiale state result before update
console.log(storeStatus); // false
// Update state
setStoreStatus(true);
return (
<>
…
</>
);
}
So, how do we access or read the state variable after updating the state? Well! It’s the same way we read the state when logging it into the console in the above example. Now let’s see what we’ll get when we log our result to the console.
// Result after update
console.log(storeStatus); // true
Destructuring the useState Hook as an Array
We’ve seen how a state can be read and updated, now let’s learn about something called destructing. What does destructuring mean?
Destructuring is a JavaScript expression that allows us to extract data from arrays and objects and set them into new variables. It simply allows us to extract multiple properties, or items, from an array at a time.
With destructuring, we can shorten our code. Remember the useState hook returns an array with two elements. Hence we can make our code shorter with the destructuring syntax:
import { useState } from "react";
const ShoeStore = () => {
// Stored state
const [storeStatus, setStoreStatus] = useState(false);
return (
<>
…
</>
);
}
Having learned what destructuring is and how to use it, we would be using destructuring henceforth when working with the useState hook in our code examples.
Updating Objects and Arrays in React useState
Updating objects and arrays in React useState is quite different from how it was done in the example above. To better explain this, let’s take a look at code examples starting with initializing our useState hook with an array.
import { useState } from "react";
const Store = () => {
// Using destructuring
const [phones, setPhones] = useState(["Nokia"]);
console.log(phones); // [“Nokia”]
setPhones([“Samsung”]);
console.log(phones); // [“Samsung”]
return (
...
);
}
There’s a way we can also update the array without losing the previous data stored in it using the spread operator.
import { useState } from "react";
const Store = () => {
const [phones, setPhones] = useState(["Nokia"]);
console.log(phones); // [“Nokia"]
setPhones([...phones, "Samsung"]);
console.log(phones); // [“Nokia", "Samsung"]
return (
...
)
}
So, how do we update an object in the useState hook? To better understand how to manage an object’s state, let’s take a look at another code example.
In the following code sample, we’ll create a state object, customerData
, and its setter, setCustomerData
. customerData
would then carry the state object’s current state while setCustomerData
will update the state value of customerData
.
import { useState } from "react";
const Store = () => {
const [customerData, setCustomerData] = useState({
id: 1,
name: ""
});
console.log(customerData); // {id: 1, name: ""}
setCustomerData(prevState => {
return {...prevState, name: "Shaydee Coder"}
}); // prevState == customerData
console.log(customerData); // {id: 1, name: "Shaydee Coder"}
return (
...
);
}
The useState hook setter also accepts a callback function as a parameter that gets access to the state’s previous value.
As seen in the above example the value of prevState
gotten from the callback function was duplicated into a returning object – the updated object, using the spread operator. This explains why we still have the id
property in our state. And the name property was modified or overridden with the string value Shaydee Coder
.
How to Update State in a Nested Object in React useState Hook
Nested objects are objects inside another object. Let’s create a customerData
object with the useState hook which will nest another object called bioData
inside it to better understand what a nested object is.
import { useState } from "react";
const Store = () => {
const [customerData, setCustomerData] = useState({
id: 1,
bioData: { name: "" }
});
console.log(customerData); // { id: 1, bioData: { name: "" } }
return (
...
);
}
Now that we have our nested object set up with the useState hook, let’s take a look at how nested objects can be updated when used in a state.
import { useState } from "react";
const Store = () => {
const [customerData, setCustomerData] = useState({
id: 1,
bioData: { name: "" }
});
// Result before updating
console.log(customerData); // { id: 1, bioData: { name: "" } }
// Updating nested object
setCustomerData((prevState) => {
return {
...prevState,
bioData: { name: "Shaydee Coder", phone: “ ” }
}
});
// Result after updating
console.log(customerData); // { id: 1, bioData: { name: "Shaydee Coder", phone: “ ” } }
return (
...
);
}
Now we know how to update objects but we still a problem. How do we properly update the phone
property in the nested bioData object without losing the name property?
Yeah, you got it right! We’re going to make use of the spread operator again to duplicate all the bioData
data inside the updating bioData
and then override the phone property as seen in a couple of examples above.
import { useState } from "react";
const Store = () => {
const [customerData, setCustomerData] = useState({
id: 1,
bioData: { name: "" }
});
// Result before updating
console.log(customerData); // { id: 1, bioData: { name: "" } }
// Updating nested object
setCustomerData((prevState) => {
return {
...prevState,
bioData: { name: "Shaydee Coder", phone: "" }
}
});
// Result after updating
console.log(customerData); // { id: 1, bioData: { name: "Shaydee Coder", phone: "" } }
// Updating phone in nested object
setCustomerData((prevState) => {
return {
...prevState,
bioData: { ...prevState.bioData, phone: "0123456789" }
}
});
// Result after updating phone in nested object
console.log(customerData); // { id: 1, bioData: { name: "Shaydee Coder", phone: "0123456789" } }
return (
...
);
}
Multiple Calls to React useState Hook
React useState hook can be called multiple times on a page. When working with forms that usually have multiple fields or values stored in a state, we have the option of organizing the state using multiple state variables.
We’ve been using the Store
component in our previous code examples without getting to the checkout page. Let’s create a CheckoutForm
to see how we can make multiple calls to the React useState hook using multiple state variables.
import { useState } from "react";
const CheckoutForm = () => {
const [customerName, setCustomerName] = useState("");
const [cardNumber, setCardNumber] = useState('');
const [CVV, setCVV] = useState(null);
return (
...
);
}
Aren’t multiple-state variables so good?
Conclusion
Now that you know how to use React useState hook, you’ll be better able to use it in your future projects.