It's time to talk about one of the most important aspects of React. That's right, you guessed it, STATE! Whether you're an experienced React developer or just dipping your toes in the water, you'll be hard pressed to avoid the concept of state within your web applications, so let's dive right in.
Beginning with the most important question, what even is state? When we talk about state, we are referring to data that changes within an application, particularly in response to user interaction. To get a better understanding, let's consider this in the context of server-side data versus client-side data. Server-side data refers to what is in our database. Perhaps we have a collection of player names for a team roster that we've stored in a database. We can send a GET request for this server-side data and display it on our page. Sounds easy enough, right? Well yes, but what if we want to update this list by adding a new player to the roster? We could provide the users with a form where they enter the first and last name of the new player and submit it to be added. Can you see the issue here?
When we submit that form and send a POST request to the database, this will successfully add the new player, but it won't be immediately reflected on the page. Rather, we will only be able to see the new player addition once we refresh the page and send a new GET request to grab the updated data. This is certainly not the most efficient, nor is it very user-friendly. If only there was a solution...
This is where React state comes in! State refers to the dynamic data within our web applications that changes in response to user engagement. It is held internally by React. In our previous example, we dealt only with a server-side data update when we added the new player. But we can also incorporate a dynamic, client-side data update using React state in order to allow the user to view this newly added data as soon as they submit the form. When we incorporate state in our applications, React will dynamically update (i.e. re-render) the relevant portions of our page in order to reflect user-initiated changes to our data. Let's see this in action through a basic example.
To start, React state employs the useState hook, so the first thing we must do is to import this hook from React.
import React, { useState } from 'react'
Next, we need to set the initial state value of whatever variable we want to dynamically change. Let's say we have a basketball player as our user, and she wants to track the number of free throws she must take in practice by starting with a "target number" and counting down. If her goal is to take 25 free throws, she can initialize her state variable at 25:
function Tracker() {
const [shots, setShots] = useState(25)
return (
<>
<p>I have {shots} free throws remaining</p>
<button>Swish!</button>
</>
)
}
Here, we can see that useState provides us with two elements in an array: the first is whatever variable we want to initialize, while the second is a function (commonly referred to as a setter function) that will allow us to update that variable. In the above example, we have deconstructed the array in order to get our primary shots variable and our setShots function. The initial value of the shots variable is set at 25. Great, are we done?!
Not quite, but almost! We need functionality to capture the actual user interaction that will cause changes in state. We want the user to be able to click the Swish! button in order to decrement the shots value. We can do this by creating a decrement function and linking that function with a user click event:
function Tracker() {
const [shots, setShots] = useState(25)
function decrement() {
setShots(shots - 1)
}
return (
<>
<p>I have {shots} free throws remaining</p>
<button onClick={decrement}>Swish!</button>
</>
)
}
In this way, any time a user clicks the Swish button, the following will happen:
decrement function is called, resulting in a call to setShots
setShots is called with (25 - 1)
Tracker component re-renders with the value of 25 - 1
shots gets updated internally by React and now holds the new value of 24
These last two bullet points introduce some important nuances about state in React. First, any time we update state, the relevant component(s) get re-rendered. In the above example, our Tracker component gets re-rendered with the value of (25 -1), which is why we as users will be able to see the 24 reflected within our <p> tag.
While out of the scope of this blog, we can hold state that is relevant to our components in other components like parent components, grandparent components, and so on. This is particularly common in cases where multiple components need to update in response to those variable changes. In this case, all components linked to that state variable would re-render in response to the change (i.e. the component where state is held as well as all child components). Thus, where you decide to hold state becomes very important as your web applications grow!
The final bullet point is a bit more nuanced and highlights the order in which React itself handles updates. Did you notice that the internal update to the state variable in React occurred last? This might not seem like a big deal, but consider if the aforementioned basketball player only wanted to update her counter on every other shot. You might be tempted to set up your decrement function like this:
function decrement() {
setShots(shots - 1)
setShots(shots - 1)
}
The problem, however, is that React's internal state has not updated the value of shots when you hit the second function call. So while you might expect the new value of shots to be 23 after the user clicks the button, it will only be 24. React only knows the current value of shots as 25, so no matter how many consecutive function calls you include, it will always display the value of 25 - 1 = 24. Therefore, it is best practice to use callback functions for any calculations that depend on previous state values. Our completed code in this case would look as follows:
function Tracker() {
const [shots, setShots] = useState(25)
function decrement() {
setShots((shots) => shots - 1)
setShots((shots) => shots - 1)
}
return (
<>
<p>I have {shots} free throws remaining</p>
<button onClick={decrement}>Swish!</button>
</>
)
}
If these last few points were a bit confusing, don't sweat it! This gets into some of the more nuanced ideas in React, and you will get a better grasp on these over time as you gain experience developing web applications and using state.
The big takeaway here is that state is an integral part of what makes React so powerful. It allows us to reflect dynamic changes to our client-side data as users engage with our applications, which we can then ultimately persist on the server-side, should we choose to do so!