React State
Why state?
Up until now, we've been able to create simple, static (unchanging) UI (User Interface - what the user actually sees) components (those that just display stuff to the user), as well as components that pass properties (props
) down to their children components to allow us to make even more complex (but still static) UI components.
But the beauty of React is its quickness in reacting (hence the name React) to changes that should occur in the UI based on the user's interaction with the page. For example, let's say you're making a todo list. You'd have an input box for the user to type the new todo item into and a button for adding that text to a list. Adding text to a list (perhaps a <ul>
element in the HTML) is a change in the UI - the DOM needs to render the new element to the page.
In the olden days of pure JS, you'd essentially have to do the following in order to get one new <li>
to show up on the page:
var todoList = document.getElementById("todo-list");
var newTodo = document.createElement("li");
var newTodoText = document.getElementById("new-todo").value;
newTodo.appendChild(newTodoText);
todoList.appendChild(newTodo);
With React, however, we approach this from a fundamentally different angle. What if instead of telling the browser, step-by-step, how to add elements to a page, we instead told React what to add, and allowed React to do the dirty work of telling the browser how to add that new thing?
This is where the idea of state comes into play. State represents the "what" of our application - the data we want to show up. By focusing just on the data, we can allow React (which was written by smarter people than us) to correctly reflect that data on the UI!
When to use state?
There will be plenty of components you'll create that won't even need state in them. These components without state are called, appropriately enough, "stateless components", and are usually only intended for displaying UI. A component that does maintain data (state) is called a "stateful component."
There's a lot more to be said about stateless and stateful components, but they're outside the scope of this post. For now, just know that a component with state is called a "stateful component" and one without state is called a "stateless component".
What is "state"?
Think about the yearly U.S. presentation given by the President called "The State of the Union Address." In these terms "state" is referring to the set of information that represents the current condition of all the things we care about. From year to year, the "state" of the Union changes.
"State" in React is essentially the same - information in our application that should indicate how things look in the UI that changes. State is always represented by a plain JavaScript object, and should contain any information that is necessary to correctly display things on the UI.
Imagine you're making a simple app that has a button and a text display that shows how many times the user has clicked the button. Since state is just a JavaScript object, you could imagine it looks something similar to this:
let state = {
clickCount: 0
}
The above state
object represents data that we're expecting to change.
How do I add state to a component?
Adding state to a component is as simple as adding a class property called state
! In ES2015, to add properties to your class-based component, you need to add a constructor method and call super()
. The reason for this is outside the scope of this blog post, so for now just know that's how you have to do it. (In the more updated ES2016, it's a bit simpler.)
class Counter extends React.Component {
constructor() {
super();
this.state = {
clickCounter: 0
}
}
render() {
return (
<h1>{this.state.clickCounter}</h1>
)
}
}
At the time of writing this post, if you want a component with state, you must use a class-based component. Functional components are currently only for creating stateless components.
In the above example, our Counter
component has data it wants to keep track of. It expects that data will be changing, so it uses state
to keep track of that data.
It's also important to note that state is local only to the component where the state is declared. If you need your state to show up in a child component, you'll need to pass the state's information down.
Remember how we pass information down to a child component? Props!
For example, maybe instead of rendering the <h1>
with the Counter
component we (for some reason) decide we need another component called CounterDisplay
whose job is to render the <h1>
. We could pass a property called counter
down to the CounterDisplay
, and set it equal to the state's clickCounter
property:
// Parent Component, which keeps track of state:
class Counter extends React.Component {
constructor() {
super();
this.state = {
clickCounter: 0
}
}
render() {
return (
//This is where we pass the state down to a child component
<CounterDisplay counter={this.state.clickCounter}></CounterDisplay>
)
}
}
// Child component, which was passed state from the parent via "props":
class CounterDisplay extends React.Component {
render() {
return (
// This component was given a prop called counter, so we access is with this.props.counter
<h1>{this.props.counter}</h1>
)
}
}
How do I change the state?
Changing state is important to know, since the whole point of state is to hold data that can be changed. In the above example, when a user clicks the button, we need to update the state.clickCounter
to be one greater than what it was before.
For simplicity, here's our original component (before we added the child component from the last example):
class Counter extends React.Component {
constructor() {
super();
this.state = {
clickCounter: 0
}
}
render() {
return (
<h1>{this.state.clickCounter}</h1>
)
}
}
Let's add a button to our component that the user can click to add one to our clickCounter
. Remember, a component must render a single parent element, so we'll need to wrap our <h1>
and new <button>
in a parent element like a <div>
. We'll also add an onClick
to our button and have it call a handleClick
method on the class (which we haven't written yet):
class Counter extends React.Component {
constructor() {
super();
this.state = {
clickCounter: 0
};
}
handleClick() {
// Some code to increment the clickCounter will go here
}
render() {
return (
<div>
<h1>{this.state.clickCounter}</h1>
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
We don't modify state directly
Our handleClick
method should update the state by adding 1 to clickCounter
. One tricky thing to learn at first is that you'll never want to change state directly. In other words, instead of writing this for your handleClick
method:
// What NOT to do:
handleClick() {
this.state.clickCounter++;
}
we'll need to do a little more work by using React's built-in setState()
method.
Using this.setState()
The setState()
method is how you can update the state. The reason we do it this way is because, under the hood, setState
lets React know that it will need to re-render something on the DOM, since some state that is being displayed on the UI is changing. If you just try to change state directly (like the example above), React will never get the memo that something on the UI needs to re-render! The state will change, but the UI won't.
There are a couple of ways to use setState()
- you can either provide an object representing the new version of the state you would like:
handleClick() {
this.setState({
clickCounter: 1
})
}
OR you can provide a callback function that returns the new state you want. The benefit of using the callback function is that it will be passed the old version of the state, in case you decide you will need that to help you to figure out the new state. In the above example where we passed a plain object, we're hardcoding in the clickCounter
to be 1 after the button is clicked. But that's not terrible useful. Instead, we should use a callback function with prevState
passed to it:
handleClick() {
this.setState(prevState => {
return {
clickCounter: prevState.clickCounter + 1
}
})
}
prevState
is a function parameter that represents what the state used to look like. We can use it to access the old version of clickCounter
and add one to it.
Notice we don't use
prevState.clickCounter++
! That would be equivalent toprevState.clickCounter = prevState.clickCounter + 1
, which you can see by the=
operator would be modifying the original state, which we just decided we shouldn't do. We're instead returning a new object that uses theprevState.clickCounter
to help us determine the brand new value ofclickCounter
in our brand new version of state.
What if I have more properties on state that I also want as part of the new state?
If you are only changing one part of a multi-faceted state
object, no problem! React handles the merging of the old object and the new one for you. Let's assume state used to look like this:
...
this.state = {
clickCounter: 0,
username: "nyancat4ever"
};
...
and you called a setState()
like this:
handleClick() {
this.setState(prevState => {
return {
clickCounter: prevState.clickCounter + 1
}
})
}
your new version of state would look like this:
this.state = {
clickCounter: 1,
username: "nyancat4ever"
};
This way you don't have to worry about all the other properties of your state, you can just modify the one part you need changed and React will handle the rest.
How do I pass state up to parent components?
React is built upon a data flow principle called "unidirectional data flow." This means that data can only flow one direction, which is down. In other words, components can pass information to their children via props
, but there is technically no way to pass state data up to ancestor components.
In practice, you'll often find yourself needing to pass some kind of information from a child to a parent. While technically you can't do this, there is a workaround: functions that are bound to the parent component!
If a parent asks a child component to get data for it, it will usually also pass a function to that child for it to execute when it gets that data. The function needs to be bound to the parent so that when it gets executed, it knows that the function belongs to the parent component but is just being executed by the child component.
Analogy: ordering pizza
Think of a literal parent (human parent) who gives their cell phone to their child and asks them to order a pizza through the pizza place's mobile app. (Maybe the parent wants to try out the cool new tech but isn't savvy enough to make it work.) The child uses the app to place the pizza order (with the parent's cell phone), and then hands the phone back to the parent with the information about the order - cost, expected delivery time, etc. still on the screen. The parent can then use that information to notify all of his/her children when they should plan on dinner being served.
Let's think about this analogy:
- Who was in charge of performing the operation? The child.
- Who did the cell phone belong to the whole time? The parent.
- Who was keeping track of the important information, like price and delivery time? The parent.
- Who does the pizza place think placed the order? The parent.
This is how React handles input from child components. It will pass information (state) to a child component (via props
), who has been delegated to perform some operation or retrieve (or modify) some kind of data from the user (maybe an input box of some kind).
The parent component will also pass a function to the child component, which the child should run when the input is gathered. The function is bound to the parent component, so it can have lines like this.setState(...)
in it because it's ultimately the parent's job to know how to update the state.
With that understanding, let's take a look at our finished parent and child components again from the first examples:
class Counter extends React.Component {
constructor() {
super();
this.state = {
clickCounter: 0
};
// BIND this function to the parent, so that when it says "this.setState" it knows the correct context
// of "this". Otherwise, the child component would run the function and the computer would think "this"
// is referring to the child instead.
this.handleClick = this.handleClick.bind(this);
}
// This method is in charge of updating the state. Since the child component has the button that should send a
// signal to update the state, this function will get passed to the child component so it knows how to do its job
handleClick() {
this.setState(prevState => {
return {
clickCounter: prevState.clickCounter + 1
}
})
}
render() {
return (
// We pass the counter from the state AND the handleClick function down to the child component so that
// knows how to act when the button gets clicked.
<CounterDisplay counter={this.state.clickCounter} handleClick={this.handleClick}/>
)
}
}
class CounterDisplay extends React.Component {
render() {
return (
<div>
<h1>{this.props.counter}</h1>
{/* The onClick is the DOM event handler. When the button
is clicked, the parent's "handleClick" method will run */}
<button onClick={this.props.handleClick}>+</button>
</div>
)
}
}
Conclusion
State is a crucial part of React to understand. The key differentiator between a static web site and a dynamic web app is the ability to maintain some sort of data on the site and react to it when it changes by updating the UI. Once you get a hang of how React expects you to use and update State, your ability to write awesome, user-data-centered web applications will skyrocket!