Today we're getting started on how stateful components work in React and look at when and why we'll use state.
We've almost made it through the first week of getting up and running on React. We have worked through JSX, building our first components, setting up parent-child relationships, and driving our component properties with React. We have one more major idea we have yet to discuss about React, the idea of state.
The state
of things
React does not allow us to modify this.props
on our components for good reason. Imagine if we passed in the title
prop to the Header
component and the Header
component was able to modify it. How do we know what the title
is of the Header
component? We set ourselves up for race-conditions, confusing data state, and it would be an all-around bad idea to modify a variable passed to a child component by a parent component.
However, sometimes a component needs to be able to update its own state. For example, setting an active
flag or updating a timer on a stopwatch, for instance.
While it's preferable to use props
as much as we can, sometimes we need to hold on to the state of a component. To handle this, React gives us the ability to hold state in our components.
state
in a component is intended to be completely internal to the Component and its children (i.e. accessed by the component and any children it used). Similar to how we access props
in a component, the state can be accessed via this.state
in a component. Whenever the state changes (via the this.setState()
function), the component will rerender.
For instance, let's say we have a simple clock component that shows the current time:
Even though this is a simple clock component, it does retain state in that it needs to know what the current time is to display. Without using state
, we could set a timer and rerender the entire React component, but other components on the page may not need rerendering... this would become a headache and slow when we integrate it into a more complex application.
Instead, we can set a timer to call rerender inside the component and change just the internal state of this component.
Let's take a stab at building this component. First, we'll create the component we'll call Clock
.
Before we get into the state, let's build the component and create the render()
function. We'll need to take into account the number and prepend a zero (0
) to the number if the numbers are smaller than 10 and set the am/pm
appropriately. The end result of the render()
function might look something like this:
class Clock extends React.Component {
render() {
const currentTime = new Date(),
hours = currentTime.getHours(),
minutes = currentTime.getMinutes(),
seconds = currentTime.getSeconds(),
ampm = hours >= 12 ? 'pm' : 'am';
return (
<div className="clock">
{
hours == 0 ? 12 :
(hours > 12) ?
hours - 12 : hours
}:{
minutes > 9 ? minutes : `0${minutes}`
}:{
seconds > 9 ? seconds : `0${seconds}`
} {ampm}
</div>
)
}
}
Alternative padding technique
Alternatively, we could use the short snippet to handle padding the clock time:
("00" + minutes).slice(-2)
But we've opted to be more clear with the previous code.
If we render our new Clock
component, we will only get a time rendered everytime the component itself rerenders. It's not a very useful clock (yet). In order to convert our static time display Clock
component into a clock that displays the time, we'll need to update the time every second.
In order to do that, we'll need to track the current time in the state of the component. To do this, we'll need to set an initial state value.
To do so, we'll first create a getTime()
function that returns a javascript object containing hours
, minutes
, seconds
and ampm
values. We will call this function to set our state.
class Clock extends React.Component {
//...
getTime() {
const currentTime = new Date();
return {
hours: currentTime.getHours(),
minutes: currentTime.getMinutes(),
seconds: currentTime.getSeconds(),
ampm: currentTime.getHours() >= 12 ? 'pm' : 'am'
}
}
// ...
}
In the ES6 class style, we can set the initial state of the component in the constructor()
by setting this.state
to a value (the return value of our getTime()
function).
constructor(props) {
super(props);
this.state = this.getTime();
}
this.state
will now look like the following object
{
hours: 11,
minutes: 8,
seconds: 11,
ampm: "am"
}
The first line of the constructor should always call
super(props)
. If you forget this, the component won't like you very much (i.e. there will be errors).
Now that we have a this.state
defined in our Clock
component, we can reference it in the render()
function using the this.state
. Let's update our render()
function to grab the values from this.state
:
class Clock extends React.Component {
// ...
render() {
const {hours, minutes, seconds, ampm} = this.state;
return (
<div className="clock">
{
hours === 0 ? 12 :
(hours > 12) ?
hours - 12 : hours
}:{
minutes > 9 ? minutes : `0${minutes}`
}:{
seconds > 9 ? seconds : `0${seconds}`
} {ampm}
</div>
)
}
}
Instead of working directly with data values, we can now update the state
of the component and separate the render()
function from the data management.
In order to update the state, we'll use a special function called: setState()
, which will trigger the component to rerender.
We need to call
setState()
on thethis
value of the component as it's a part of theReact.Component
class we are subclassing.
In our Clock
component, let's use the native setTimeout()
JavaScript function to create a timer to update the this.state
object in 1000 milliseconds. We'll place this functionality in a function as we'll want to call this again.
class Clock extends React.Component {
// ...
constructor(props) {
super(props);
this.state = this.getTime();
}
// ...
componentDidMount() {
this.setTimer();
}
// ...
setTimer() {
clearTimeout(this.timeout);
this.timeout = setTimeout(this.updateClock.bind(this), 1000);
}
// ...
updateClock() {
this.setState(this.getTime, this.setTimer);
}
// ...
}
To start updating the timer immediately after the our component has been rendered, we call
this.setTimer()
in a React component lifecycle method calledcomponentDidMount
.We will get into the lifecycle hooks in the next section.
In the updateClock()
function we'll want to update the state with the new time. We can now update the state in the updateClock()
function:
class Clock extends React.Component {
// ...
updateClock() {
this.setState(this.getTime, this.setTimer);
}
// ...
}
The component will be mounted on the page and will update the time every second (approximately every 1000 milliseconds)
Now the component itself might rerender slower than the timeout function gets called again, which would cause a rerendering bottleneck and needlessly using up precious battery on mobile devices. Instead of calling the setTimer()
function after we call this.setState()
, we can pass a second argument to the this.setState()
function which will be guaranteed to be called after the state has been updated.
class Clock extends React.Component {
// ...
updateClock() {
const currentTime = new Date();
this.setState({
currentTime: currentTime
}, this.setTimer);
}
// ...
}
Here is our full Clock
component code.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = this.getTime();
}
componentDidMount() {
this.setTimer();
}
setTimer() {
clearTimeout(this.timeout);
this.timeout = setTimeout(this.updateClock.bind(this), 1000);
}
updateClock() {
this.setState(this.getTime, this.setTimer);
}
getTime() {
const currentTime = new Date();
return {
hours: currentTime.getHours(),
minutes: currentTime.getMinutes(),
seconds: currentTime.getSeconds(),
ampm: currentTime.getHours() >= 12 ? 'pm' : 'am'
}
}
render() {
const {hours, minutes, seconds, ampm} = this.state;
return (
<div className="clock">
{hours == 0 ? 12 : hours > 12 ? hours - 12 : hours}:
{minutes > 9 ? minutes : `0${minutes}`}:
{seconds > 9 ? seconds : `0${seconds}`} {ampm}
</div>
);
}
}
Styles
As we're not focusing on CSS in this course, we're not covering the CSS specific to build the clock as you see it on the screen.
However, we want to make sure the clock you build looks similar to ours. If you include the following CSS as a
<link />
tag in your code, your clock will look similar and will be using the same styling ours is using:<link href="https://cdn.jsdelivr.net/gh/fullstackreact/30-days-of-react@master/day-06/public/Clock.css" rel="stylesheet" type="text/css" />
Some things to keep in mind
-
When we call
this.setState()
with an object argument, it will perform a shallow merge of the data into the object available viathis.state
and then will rerender the component. -
We generally only want to keep values in our state that we'll use in the
render()
function. From the example above with our clock, notice that we stored thehours
,minutes
, andseconds
in our state. It's usually a bad idea to store objects or calculations in the state that we don't plan on using in therender
function as it can cause unnecessary rendering and wasteful CPU cycles.
As we noted at the top of this section, it's preferred to use props
when available not only for performance reasons, but because stateful components are more difficult to test.
Today, we've updated our components to be stateful and now have a handle on how to make a component stateful when necessary. Tomorrow we'll dive into the lifecycle of a component and when/how to interact with the page.