State in React
To truly master React (and Redux), it is important to understand the concept of state, its relationship with components, and why it is a valuable tool in our coding arsenal.
So, what is state exactly?
At their most basic level, React components are modular, reusable chunks of code that take in data and use that data to render HTML. One way to get data to a component is through props. But this strategy has limitations — because props are difficult to change: a parent component would need to pass down new props before this could be possible.
State, on the other hand, is a plain JavaScript object that contains data that can be changed or mutated within the component itself. It doesn’t need to rely on its parent.
Let’s take a look at an example from my project, BoardGameShelf:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addGameToShelf } from '../actions/gameShelf';class AddGameButton extends Component {
state = {
buttonText: "Add To Shelf",
disabled: false
}
render(){
const userGames = this.props.currentUser.attributes.games
const currentGame = this.props.game
const ids = this.props.ids const handleClick = event => {
event.preventDefault();
this.setState({
buttonText: "SHELVED!",
disabled: true
})
this.props.addGameToShelf(ids)
} if(userGames.some(game => game.name === currentGame.name)){
return(
<button
className="btn btn-secondary"
disabled={true}
>Shelved!</button>
)
} else {
return(
<button
className="btn btn-secondary"
onClick={handleClick}
disabled={this.state.disabled}
>{this.state.buttonText}</button>
)
}
}
}export default connect(null, { addGameToShelf })(AddGameButton);
There’s a lot happening in this code, most of it unrelated to the discussion of state, so I have bolded the relevant information.
This component renders a button that allows users to add a board game from a universal database to their own private game shelf. When a user clicks this button, I wanted this change to be reflected visually — switching the button text from “Add To Shelf” to “Shelved!”. I also wanted to prevent the user from adding the same game to their library twice, disabling the button on a click. A local state was perfect to accomplish this task.
First, I set my initial state. The initial state is the data we want to be reflected when the component first loads, the data before it has been changed. In this case, I set my state object to include the two elements necessary to achieve my above goal: buttonText is set to “Add to Shelf” and disabled is set to false.
Since I want to change my state when the button it is clicked, I built out a function called handleClick. This function takes a number of actions, but for our purposes, it sets our new state, changing the initial state to reflect new information. We accomplish this using the setState() method, which queues up changes to our component’s state, requesting that React make our update and re-render the component with the new data in mind.
You’ll notice that I said that setState() is a request, not an immediate update of our initial state. That is because setState() is asynchronous. The changes aren’t made until after the component completes any current tasks. For that reason, it would be a mistake to assume that you could call setState() on one line of code and attempt to use the updated state on the next line.
Luckily, in this case, that should not be an issue. In handleClick, I call setState(), setting buttonText to “SHELVED!” and disabled to true. If the current game’s name does not match any of the games currently on the user’s shelf, I render a button in HTML and pass in my handleClick function to onClick. I also use my disabled state to determine whether or not disabled is true or false. Finally, the button displays the buttonText using the current state.
With everything working, we get exactly the result I was looking for!
With a single click, using a change of state, the button can no longer be clicked and it informs the user that the item has been added to their personal shelf. Great!
Now that we understand the fundamentals of how to initialize and set a new state, let’s focus on a few important details.
First, you should never modify your state directly. Always use setState() to make your changes.
### DO THISthis.setState({ buttonText: "SHELVED!", disabled: true })### DON'T DO THISthis.state.buttonText = "SHELVED!"
this.state.disabled = true
This rule of thumb is more than just convention. If you modify state directly, there is a good chance that those changes could be overwritten. You’ll also lose control of your state across components. Either of these situations can cause errors or your data to be inaccurately reflected on render. As you write more and more code, scaling up, this strategy becomes increasingly unmanageable. As such, just use setState(), it is the cleanest way to update the state of a component.
Lastly, updates to state are merged into the current state when you call setState(). This means that individual variables in a state object can be updated independently.
state = {
buttonText: "Add To Shelf",
disabled: false
}this.setState({ disabled: true })//=> returns updated state = {
buttonText: "Add To Shelf",
disabled: true
}
In this example, we can see that when I call setState(), we are only updating the disabled variable. This merges with the data from our initial state, leaving this.state.buttonText in tact while replacing this.state.disabled. In other words, the button’s display text won’t change, but it will still be disabled on click.
Local state is a powerful tool for making changes to the ways in which our data is presented to the user on the frontend. However, the more often we use state within an application, the more complex it becomes, making it difficult to keep track of all of our updates. As such, we should strive to make our components stateless when possible. As in just about everything, moderation is best.
A great way around this problem is through the use of Redux, which takes the data necessary for use across our application and separates it out from individual components. We will discuss Redux and state in my next post.