Welcome back, fellow coders!
Today we're going over the basics of a React Hook called useState. useState is a powerful tool that we can use to make the data more dynamic while building applications.
It might take you a little practice before this hook is second nature, but don't worry, in the long run it will make your job so much easier!
What is useState?
useState is a hook that returns an array when invoked. The array returns two powerful elements (in the examples in this article, we will use destructuring to assign those elements to variables): First up in the array is our State that will hold our dynamic data. We can update this variable with the second item in our array: a "setter" function (we invoke this function and it "sets" State to whatever we specify). And before we start using this array, we need to take a few steps:
1. Import {useState}:
As mentioned earlier, useState is a React Hook and before we can use it, we first need to import it into the component we wish to use it with. We do that like so:
import React, { useState } from “react”;
[Notice how useState is placed inside the curly braces (object destructuring)].
2. Declare State Variables Inside Your component:
Next, we need to declare our State variables inside the component we want to use it in.
function OurComponent(){
const [variable, setVariable] = useState("initial value goes here");
//more code to come...
}
[We've declared our State variables, but in the example above, it's important for you to understand that we are using array destructuring, which, in short, is a way we can "separate" values from arrays into separate variables to use].
In the above code snippet, we can see how the array's first item is a variable named "variable":
const [variable, setVariable] = useState("initial value goes here");
We will use this variable later in our code just like we would any other variable.
Next, we've declared our setter function which will allow us to "update" the State variable ("setVariable"):
const [variable, setVariable] = useState("initial value goes here");
Finally, we've set an initial value for our State:
const [variable, setVariable] = useState("initial value goes here");
(Intial values can be strings, objects, etc.)
3. Calling Our Stateful Variable:
As mentioned earlier, we can use the State variable like a normal variable. When we call it in our JSX, we simply can put curly braces around it like so:
<h1>{variable}<h1>
This will display the following h1 in our browser:
By following these simple steps, we have opened our application to many possibilities. Now let's look at an example of how State can benefit us.
useState Example:
Recently I was creating a simple application wherein a user could access a form that would allow them to add key information about a Bible verse, then save it for later reference in two other pages of the application.
By using State I was able to create a controlled form that allowed my application to "grab" the value from an input field and hold it in State.
Inside my Form component, these were my Stateful variables:
const [testament, setTestament] = useState("");
const [reference, setReference] = useState("");
const [verse, setVerse] = useState("");
const [url, setUrl] = useState("");
Now let's take a look at my input fields inside my "form" tag:
<form onSubmit={handleSubmit}>
<input type="text" value={testament} placeholder={"Type 'Old' or 'New'"} onChange={(e)=> setTestament(e.target.value)} className="form"></input>
<br></br>
<input type="text" value={reference} placeholder={"Example: John 3:16"} onChange={(e)=> setReference(e.target.value)} className="form"></input>
<br></br>
<input type="text" value={verse} placeholder={"Example: For God so loved the world... "} onChange={(e)=> setVerse(e.target.value)} className="form"></input>
<br></br>
<input type="text" value={url} placeholder={"Paste the URL here"} onChange={(e)=> setUrl(e.target.value)} className="form"></input>
<br></br>
<button>Add My Verse!</button>
</form>
Let's take a closer look at the input fields:
<input type="text" value={testament} placeholder={"Type 'Old' or 'New'"} onChange={(e)=> setTestament(e.target.value)} className="form"></input>
A few important things to note: first, the input's "value" is set to our State variable. Second, we have an "onChange" event handler that passes the callback function that calls the setter function "setTestament" and passes it "e.target.value" (which is whatever a user types into the input field). These two items, along with the declared State variable of "testament" that we defined earlier, allow us to dynamically update the "testament" variable so it reflects whatever a user has typed into the input form.
Since State is holding the value for "testament", we can later call the "testament" variable in our code.
For example, in my Bible Verse Application, I wanted to make a POST request, and thanks to State, I could now use the values stored in State to create a body for my POST request. Take a look at how I used the values stored in State to create my POST request's body. You'll notice that I named the State variables the same thing as the keys in my API objects, so some of the keys/value pairs look like they are repetitive. In reality, the key is the word on the left, and the State variable is being called as the value on the right.
const configObj = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(
{
testament: testament,
//The API Object Keys and the Stateful variables are named the same thing. The Keys are on the left, and the Stateful variable is being called on the right.
reference: reference,
verse: verse,
url: url
}
)
}
fetch("http://localhost:3000/verses", configObj)
.then(r=>r.json())
.then(verseObj => {
addVerse(verseObj);
})
}
Now that the information I stored in my State variables (which was retrieved from the input fields) is no longer needed in my component (because an Object has been added to the API with the information), I can clear the input fields by simply calling the setter functions and setting them to an empty string:
fetch("http://localhost:3000/verses", configObj)
.then(r=>r.json())
.then(verseObj => {
addVerse(verseObj);
//Now that I am sure my POST request was successful, I can clear the values stored in State so they will be ready for new user input:
setTestament("");
setReference("");
setVerse("");
setUrl("");
})
This is just one example of how useful and time-saving useState can be while we code. However, useState does have an idiosyncrasy that we need to be aware of when we begin to use State.
useState Is Asynchronous:
When using State, we need to be aware that whenever our setter function is called, the component will re-render. Take a moment to think about what you just read.
useState's ability to re-render the component is a good thing: it allows us to update the DOM without making the user manually refresh the page to show an updated DOM.
You'll want to remember that updating State is asynchronous. This means that React will internally keep track of the new value when a setter function is called, but it won’t update the State "variable" until after the component re-renders. Because of asynchrony, the rest of the component will finish running before the re-rendering occurs.
Okay, let's take a look at an example to make sure we understand: If we have a State variable like in the example below, and we call the setter functions, take a look at when the console.logs print.
Our code:
import React, {useState} from "react";
function OurComponent (){
const [variable, setVariable] = useState("")
console.log("1: ", variable)
function handleChange(e){
setVariable(e)
console.log("2: ", variable)
}
console.log("3: ", variable)
return(
<div>
<input type="text" value={variable} onChange={e => handleChange(e.target.value)}></input>
<h3>{variable}</h3>
</div>
)
}
export default OurComponent;
Let's take a closer look at these console.logs to truly understand the asynchronous behavior of State:
In our first console.logs we did not see our "2" console.log. This was expected, as handleChange had not yet been called, so let's move on.
Our "2" console.log was the first to be called, but even though we had updated State in the prior line in our code, the change was not reflected in the "2" console.log. What we learned from our first console.logs was that "1" and "3" are called whenever the page re-renders and we do indeed see them with the update value after re-rendering.
So what happens to our "2" console.log if we type another character in our input field? We see that "2" is updated with the value from our previous entry... but "1" and "3" have re-rendered once again and are displaying the correct value that is stored in our State "variable".
Let's Recap:
Congratulations! You now have all the tools to get started in useState. In this article, we discussed a little about why we like useState and then went over the necessary code to get useState working in your application. We also delved into an example of a controlled form that utilized State to keep track of input fields and send a POST request to an API. Finally, we covered one of useState’s unique attributes that can cause issues if we aren’t careful, so we'll remember that State is asynchronous.
You are all ready to begin coding with useState, but in case you need a cheat sheet to get started:
Import the useState hook:
import React, { useState } from “react”;
Declare the State variables inside the Component:
function OurComponent(){
const [variable, setVariable] = useState("initial value goes here");
//The rest of your code goes here...
}
Call the setter function when you want to update the State variable:
setVariable("new value goes here");
Top comments (0)