- Use the
useState
hook to create state within a component - Update state based on events
In this code-along, we'll get some hands-on practice writing components with state and setting state based on different kinds of events.
To get some practice adding state to a component, code along with this lesson.
There's some starter code in the src/components
folder. We'll be adding state
to some existing components and building out some functionality that depends on
state.
As a quick recap:
State is data that changes over time in your component. State must be initialized in a component by calling
useState
. Updating state by callingsetState
will cause our components to re-render automatically.
To code along, run npm install && npm start
to install the dependencies and
get the demo app running.
For our first component, let's work on a toggle button. It should do the following:
- The button should say "OFF" when it is first displayed
- When the button is clicked, it should say "ON"
- When the button is clicked again, it should say "OFF"
- etc
Let's talk through the steps and how we might think about building a feature like this as a React developer.
First, let's decide: do we actually need state for this feature? We need to determine if the data for this feature is static (doesn't change) or dynamic (does change). If it's dynamic, we'll definitely need state! We should also ask if this feature could be made using props instead of state.
Here are some questions from Thinking in React that will help us decide if we need state:
- Is it passed in from a parent via props? If so, it probably isn’t state.
- Can you compute it based on any other state or props in your component? If so, it isn’t state.
- Does it remain unchanged over time? If so, it probably isn’t state.
Since this component isn't being passed any props that will let us display some new button text, and the button's text is dynamic (it changes), we definitely need to add state!
Our full checklist looks like this:
- 🚫 Is it passed as a prop?
- 🚫 Can you compute it based on any other state or props in your component?
- 🚫 Does it remain unchanged over time?
So it's time to add state! There's some starter code in the Toggle.js
file. If
you're feeling good about what you learned in the last lesson, give it a shot
now! We'll walk through the steps below.
...
...
...
...
Ok, hope you were able to get that going! Here's our process for adding state to build out this feature.
Any time we need state in a component, we need to use the useState
hook from
React. We can import it like so:
import React, { useState } from "react";
To create a state variable in our component, we need to call useState
and
provide an initial value:
function Toggle() {
const [isOn, setIsOn] = useState(false);
// ... the rest of Toggle component
}
Whenever you're using a React hook, it must be within a React component.
We're setting the initial state here as false
, because the button should be
"OFF" when the component first renders.
Now that we have this new variable, it's time to use it! We can use the isOn
variable to determine what text to display in the button:
<button>{isOn ? "ON" : "OFF"}</button>
Here, we're doing some conditional rendering to dynamically determine the button's text based on our state variable.
You should now be able to change the initial state in the useState
function
and see if your button's text displays what you expect. Setting an initial state
of true
should display "ON", and false
should display "OFF".
Any time we want to update state, we need to use the setter function
returned by calling useState
. We also need to determine what triggers that
update. In our case it's the button being clicked.
Let's start by adding an onClick
handler to the button:
<button onClick={handleClick}>{isOn ? "ON" : "OFF"}</button>
Next, let's set up the handleClick
callback function, and update state. Here,
we must call the setter function to update our state variable. Trying to
update the variable directly won't have any effect (even if we changed our
variable declaration to let
instead of const
):
let [isOn, setIsOn] = useState(false);
function handleClick() {
// updating state directly is a no-no!
isOn = !isOn;
}
So the way we should update state looks like this:
function handleClick() {
setIsOn((isOn) => !isOn);
}
All together, here's our updated component:
function Toggle() {
const [isOn, setIsOn] = useState(false);
function handleClick() {
setIsOn((isOn) => !isOn);
}
return <button onClick={handleClick}>{isOn ? "ON" : "OFF"}</button>;
}
With this state variable in place, let's add another feature to our button. When the button is ON, let's make the background red, like this:
<button style={{ background: "red" }}>
When it's OFF, it should have a white background.
Let's go through those same questions to determine if we need to add state for this feature.
- 🚫 Is it passed as a prop?
- ✅ Can you compute it based on any other state or props in your component?
- 🚫 Does it remain unchanged over time?
In this case, we can compute it based on other state in our component, so we don't need to add any new state for this feature.
We can use that same isOn
state variable, along with some conditional
rendering, to get the button to display the correct border color:
function Toggle() {
const [isOn, setIsOn] = useState(false);
function handleClick() {
setIsOn((isOn) => !isOn);
}
const color = isOn ? "red" : "white";
return (
<button style={{ background: color }} onClick={handleClick}>
{isOn ? "ON" : "OFF"}
</button>
);
}
Thinking like a React developer involves making a lot of decisions about how to structure your components, particularly when it comes to props and state. Now that you've seen the process and some common patterns for working with state, it's up to you to apply these decisions to your own components moving forward.