In the last chapter, we talked about how to create components. We primarily focused on how to build a user interface by composing React components. This chapter is filled with techniques that you can use to better manage data and reduce time spent debugging applications.
Data handling within component trees is one of the key advantages of working with React. There are techniques that you can use when working with data in React components that will make your life much easier in the long run. Our applications will be easier to reason about and scale if we can manage data from a single location and construct the UI based on that data.
JavaScript is a loosely typed language, which means that the data type of a variable’s value can change. For example, you can initially set a JavaScript variable as a string, then change its value to an array later, and JavaScript will not complain. Managing our variable types inefficiently can lead to a lot of time spent debugging applications.
React components provide a way to specify and validate property types. Using this feature will greatly reduce the amount of time spent debugging applications. Supplying incorrect property types triggers warnings that can help us find bugs that may have otherwise slipped through the cracks.
React has built-in automatic property validation for the variable types, as shown in Table 6-1.
Type | Validator |
---|---|
Arrays | React.PropTypes.array |
Boolean | React.PropTypes.bool |
Functions | React.PropTypes.func |
Numbers | React.PropTypes.number |
Objects | React.PropTypes.object |
Strings | React.PropTypes.string |
In this section, we will create a Summary
component for our recipes. The Summary
component will display the title of the recipe along with counts for both ingredients and steps (see Figure 6-1).
In order to display this data, we must supply the Summary
component with three properties: a title, an array of ingredients, and an array of steps. We want to validate these properties to make sure the first is a string and the others are arrays, and supply defaults for when they are unavailable. How to implement property validation depends upon how components are created. Stateless functional components and ES6 classes have different ways of implementing property validation.
First, let’s look at why we should use property validation and how to implement it in components created with React.createClass
.
We need to understand why it is important to validate component property types. Consider the following implementation for the Summary
component:
const
Summary
=
createClass
({
displayName
:
"Summary"
,
render
()
{
const
{
ingredients
,
steps
,
title
}
=
this
.
props
return
(
<
div
className
=
"summary"
>
<
h1
>
{
title
}
<
/h1>
<
p
>
<
span
>
{
ingredients
.
length
}
Ingredients
<
/span> |
<
span
>
{
steps
.
length
}
Steps
<
/span>
<
/p>
<
/div>
)
}
})
The Summary
component destructures ingredients
, steps
, and title
from the properties object and then constructs a UI to display that data. Since we expect both ingredients
and steps
to be arrays, we use Array.length
to count the array’s items.
What if we rendered this Summary
component accidentally using strings?
render
(
<
Summary
title
=
"Peanut Butter and Jelly"
ingredients
=
"peanut butter, jelly, bread"
steps
=
"spread peanut butter and jelly between bread"
/>
,
document
.
getElementById
(
'react-container'
)
)
JavaScript will not complain, but finding the length will count the number of characters in each string (Figure 6-2).
The output of this code is odd. No matter how fancy your peanut butter and jelly might be, it’s doubtful that you are going to have 27 ingredients and 44 steps. Instead of seeing the correct number of steps and ingredients, we are seeing the length in characters of each string. A bug like this is easy to miss. If we validated the property types when we created the Summary
component, React could catch this bug for us:
const
Summary
=
createClass
(
{
displayName
:
"Summary"
,
propTypes
:
{
ingredients
:
PropTypes
.
array
,
steps
:
PropTypes
.
array
,
title
:
PropTypes
.
string
}
,
render
(
)
{
const
{
ingredients
,
steps
,
title
}
=
this
.
props
return
(
<
div
className
=
"summary"
>
<
h1
>
{
title
}
<
/
h
1
>
<
p
>
<
span
>
{
ingredients
.
length
}
Ingredients
|
<
/
s
p
a
n
>
<
span
>
{
steps
.
length
}
Steps
<
/
s
p
a
n
>
<
/
p
>
<
/
d
i
v
>
)
}
}
)
Using React’s built-in property type validation, we can make sure that both ingredients
and steps
are arrays. Additionally, we can make sure that the title
value is a string. Now when we pass incorrect property types, we will see an error (Figure 6-3).
What would happen if we rendered the Summary
component without sending it any properties?
render
(
<
Summary
/>
,
document
.
getElementById
(
'react-container'
)
)
Rendering the Summary
component without any properties causes a JavaScript error that takes down the web app (Figure 6-4).
This error occurs because the type of the ingredients
property is undefined, and undefined is not an object that has a length property like an array or a string. React has a way to specify required properties. When those properties are not supplied, React will trigger a warning in the console:
const
Summary
=
createClass
(
{
displayName
:
"Summary"
,
propTypes
:
{
ingredients
:
PropTypes
.
array
.
isRequired
,
steps
:
PropTypes
.
array
.
isRequired
,
title
:
PropTypes
.
string
}
,
render
(
)
{
...
}
}
)
Now when we render the Summary
component without any properties, React directs our attention to the problem with a console warning just before the error occurs. This makes it easier to figure out what went wrong (Figure 6-5).
The Summary
component expects an array for ingredients
and an array for steps
, but it only uses the length
property of each array. This component is designed to display counts (numbers) for each of those values. It may make more sense to refactor our code to expect numbers instead, since the component doesn’t actually need arrays:
import
{
createClass
,
PropTypes
}
from
'react'
export
const
Summary
=
createClass
(
{
displayName
:
"Summary"
,
propTypes
:
{
ingredients
:
PropTypes
.
number
.
isRequired
,
steps
:
PropTypes
.
number
.
isRequired
,
title
:
PropTypes
.
string
}
,
render
(
)
{
const
{
ingredients
,
steps
,
title
}
=
this
.
props
return
(
<
div
className
=
"summary"
>
<
h1
>
{
title
}
<
/
h
1
>
<
p
>
<
span
>
{
ingredients
}
Ingredients
<
/
s
p
a
n
>
|
<
span
>
{
steps
}
Steps
<
/
s
p
a
n
>
<
/
p
>
<
/
d
i
v
>
)
}
}
)
Using numbers for this component is a more flexible approach. Now the Summary
component simply displays the UI; it sends the burden of actually counting ingredients or steps further up the component tree to a parent or ancestor.
Another way to improve the quality of components is to assign default values for properties.1 The validation behavior is similar to what you might expect: the default values you establish will be used if other values are not provided.
Let’s say we want the Summary
component to work even when the properties are not supplied:
import
{
render
}
from
'react-dom'
render
(
<
Summary
/>
,
document
.
getElementById
(
'react-container'
))
With createClass
, we can add a method called getDefaultProps
that returns default values for properties that are not assigned:
const
Summary
=
createClass
(
{
displayName
:
"Summary"
,
propTypes
:
{
ingredients
:
PropTypes
.
number
,
steps
:
PropTypes
.
number
,
title
:
PropTypes
.
string
}
,
getDefaultProps
(
)
{
return
{
ingredients
:
0
,
steps
:
0
,
title
:
"[untitled recipe]"
}
}
,
render
(
)
{
const
{
ingredients
,
steps
,
title
}
=
this
.
props
return
(
<
div
className
=
"summary"
>
<
h1
>
{
title
}
<
/
h
1
>
<
p
>
<
span
>
{
ingredients
}
Ingredients
|
<
/
s
p
a
n
>
<
span
>
{
steps
}
Steps
<
/
s
p
a
n
>
<
/
p
>
<
/
d
i
v
>
)
}
}
Now when we try to render this component without properties, we will see some default data instead, as in Figure 6-6.
Using default properties can extend the flexibility of your component and prevent errors from occurring when your users do not explicitly require every property.
React’s built-in validators are great for making sure that your variables are required and typed correctly. But there are instances that require more robust validation. For example, you may want to make sure that a number is within a specific range or that a value contains a specific string. React provides a way to build your own custom validation for such cases.
Custom validation in React is implemented with a function. This function should either return an error when a specific validation requirement is not met or null
when the property is valid.
With basic property type validation, we can only validate a property based on one condition. The good news is that the custom validator will allow us to test the property in many different ways. In this custom function, we’ll first check that the property’s value is a string. Then we’ll limit its length to 20 characters (Example 6-2).
propTypes
:
{
ingredients
:
PropTypes
.
number
,
steps
:
PropTypes
.
number
,
title
:
(
props
,
propName
)
=>
(
typeof
props
[
propName
]
!==
'string'
)
?
new
Error
(
"A title must be a string"
)
:
(
props
[
propName
]
.
length
>
20
)
?
new
Error
(
`
title is over 20 characters
`
)
:
null
}
All property type validators are functions. To implement our custom validator, we will set the value of the title
property, under the propTypes
object, to a callback function. When rendering the component, React will inject the props
object and the name of the current property into the function as arguments. We can use those arguments to check the specific value for a specific property.
In this case, we first check the title to make sure it is a string. If the title is not a string, the validator returns a new error with the message: “A title must be a string.” If the title is a string, then we check its value to make sure it is not longer than 20 characters. If the title is under 20 characters, the validator function returns null
. If the title is over 20 characters, then the validator function returns an error. React will capture the returned error and display it in the console as a warning.
Custom validators allow you to implement specific validation criteria. A custom validator can perform multiple validations and only return errors when specific criteria are not met. Custom validators are a great way to prevent errors when using and reusing your components.
In the previous sections, we discovered that property validation and default property values can be added to our component classes using React.createClass
. This type checking also works for ES6 classes and stateless functional components, but the syntax is slightly different.
When working with ES6 classes, propTypes
and defaultProps
declarations are defined on the class instance, outside of the class body. Once a class is defined, we can set the propTypes
and defaultProps
object literals (Example 6-3).
class
Summary
extends
React
.
Component
{
render
()
{
const
{
ingredients
,
steps
,
title
}
=
this
.
props
return
(
<
div
className
=
"summary"
>
<
h1
>
{
title
}
<
/h1>
<
p
>
<
span
>
{
ingredients
}
Ingredients
|
<
/span>
<
span
>
{
steps
}
Steps
<
/span>
<
/p>
<
/div>
)
}
}
Summary
.
propTypes
=
{
ingredients
:
PropTypes
.
number
,
steps
:
PropTypes
.
number
,
title
:
(
props
,
propName
)
=>
(
typeof
props
[
propName
]
!==
'string'
)
?
new
Error
(
"A title must be a string"
)
:
(
props
[
propName
].
length
>
20
)
?
new
Error
(
`title is over 20 characters`
)
:
null
}
Summary
.
defaultProps
=
{
ingredients
:
0
,
steps
:
0
,
title
:
"[recipe]"
}
The propTypes
and defaultProps
object literals can also be added to stateless functional components (Example 6-4).
const
Summary
=
({
ingredients
,
steps
,
title
})
=>
{
return
<
div
>
<
h1
>
{
title
}
<
/h1>
<
p
>
{
ingredients
}
Ingredients
|
{
steps
}
Steps
<
/p>
<
/div>
}
Summary
.
propTypes
=
{
ingredients
:
React
.
PropTypes
.
number
.
isRequired
,
steps
:
React
.
PropTypes
.
number
.
isRequired
}
Summary
.
defaultProps
=
{
ingredients
:
1
,
steps
:
1
}
With a stateless functional component, you also have the option of setting default properties directly in the function arguments. We can set default values for ingredients
, steps
, and title
when we destructure the properties object in the function arguments as follows:
const
Summary
=
(
{
ingredients
=
0
,
steps
=
0
,
title
=
'[recipe]'
}
)
=>
{
return
<
div
>
<
h1
>
{
title
}
<
/
h
1
>
<
p
>
{
ingredients
}
Ingredients
|
{
steps
}
Steps
<
/
p
>
<
/
d
i
v
>
}
Property validation, custom property validation, and the ability to set default property values should be implemented in every component. This makes the component easier to reuse because any problems with component properties will show up as console warnings.
References, or refs, are a feature that allow React components to interact with child elements. The most common use case for refs is to interact with UI elements that collect input from the user. Consider an HTML form
element. These elements are initially rendered, but the users can interact with them. When they do, the component should respond appropriately.
For the rest of this chapter, we are going to be working with an application that allows users to save and manage specific hexadecimal color values. This application, the color organizer, allows users to add colors to a list. Once a color is in the list, it can be rated or removed by the user.
We will need a form to collect information about new colors from the user. The user can supply the color’s title and hex value in the corresponding fields. The AddColorForm
component renders the HTML with a text input and a color input for collecting hex values from the color wheel (Example 6-5).
import
{
Component
}
from
'react'
class
AddColorForm
extends
Component
{
render
()
{
return
(
<
form
onSubmit
=
{
e
=>
e
.
preventDefault
()}
>
<
input
type
=
"text"
placeholder
=
"color title..."
required
/>
<
input
type
=
"color"
required
/>
<
button
>
ADD
<
/button>
<
/form>
)
}
}
The AddColorForm
component renders an HTML form that contains three elements: a text input for the title, a color input for the color’s hex value, and a button to submit the form. When the form is submitted, a handler function is invoked where the default form event is ignored. This prevents the form from trying to send a GET request once submitted.
Once we have the form rendered, we need to provide a way to interact with it. Specifically, when the form is first submitted, we need to collect the new color information and reset the form’s fields so that the user can add more colors. Using refs, we can refer to the title
and color
elements and interact with them (Example 6-6).
import
{
Component
}
from
'react'
class
AddColorForm
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
submit
=
this
.
submit
.
bind
(
this
)
}
submit
(
e
)
{
const
{
_title
,
_color
}
=
this
.
refs
e
.
preventDefault
(
)
;
alert
(
`
New Color:
${
_title
.
value
}
${
_color
.
value
}
`
)
_title
.
value
=
''
;
_color
.
value
=
'#000000'
;
_title
.
focus
(
)
;
}
render
(
)
{
return
(
<
form
onSubmit
=
{
this
.
submit
}
>
<
input
ref
=
"_title"
type
=
"text"
placeholder
=
"color title..."
required
/
>
<
input
ref
=
"_color"
type
=
"color"
required
/
>
<
button
>
ADD
<
/
b
u
t
t
o
n
>
<
/
f
o
r
m
>
)
}
}
We needed to add a constructor to this ES6 component class because we moved submit
to its own function. With ES6 component classes, we must bind the scope of the component to any methods that need to access that scope with this
.
Next, in the render
method, we’ve set the form’s onSubmit
handler by pointing it to the component’s submit
method. We’ve also added ref
fields to the components that we want to reference. A ref is an identifier that React uses to reference DOM elements. Creating _title
and _color
ref
attributes for each input means that we can access those elements with this.refs_title
or this.refs_color
.
When the user adds a new title, selects a new color, and submits the form, the component’s submit
method will be invoked to handle the event. After we prevent the form’s default submit behavior, we send the user an alert that echoes back the data collected via refs. After the user dismisses the alert, refs are used again to reset the form values and focus on the title field.
When using React.createClass
to create your components, there is no need to bind the this
scope to your component methods. React.createClass
automatically binds the this
scope for you.
It’s nice to have a form that echoes back input data in an alert, but there is really no way to make money with such a product. What we need to do is collect data from the user and send it somewhere else to be handled. This means that any data collected may eventually make its way back to the server, which we will cover in Chapter 12. First, we need to collect the data from the form component and pass it on.
A common solution for collecting data from a React component is inverse data flow.2 It is similar to, and sometimes described as, two-way data binding. It involves sending a callback function to the component as a property that the component can use to pass data back as arguments. It’s called inverse data flow because we send the component a function as a property, and the component sends data back as function arguments.
Let’s say we want to use the color form, but when a user submits a new color we want to collect that information and log it to the console.
We can create a function called logColor
that receives the title and color as arguments. The values of those arguments can be logged to the console. When we use the AddColorForm
, we simply add a function property for onNewColor
and set it to our logColor
function. When the user adds a new color, logColor
is invoked, and we’ve sent a function as a property:
const
logColor
=
(
title
,
color
)
=>
console
.
log
(
`New Color:
${
title
}
|
${
value
}
`
)
<
AddColorForm
onNewColor
=
{
logColor
}
/>
To ensure that data is flowing properly, we will invoke onNewColor
from props
with the appropriate data:
submit
(
)
{
const
{
_title
,
_color
}
=
this
.
refs
this
.
props
.
onNewColor
(
_title
.
value
,
_color
.
value
)
_title
.
value
=
''
_color
.
value
=
'#000000'
_title
.
focus
(
)
}
In our component, this means that we’ll replace the alert
call with a call to this.props.onNewColor
and pass the new title and color values that we have obtained through refs.
The role of the AddColorForm
component is to collect data and pass it on. It is not concerned with what happens to that data. We can now use this form to collect color data from users and pass it on to some other component or method to handle the collected data:
<
AddColorForm
onNewColor
=
{(
title
,
color
)
=>
{
console
.
log
(
`TODO: add new
${
title
}
and
${
color
}
to the list`
)
console
.
log
(
`TODO: render UI with new Color`
)
}}
/>
When we are ready, we can collect the information from this component and add the new color to our list of colors.
In order to make two-way data binding optional, you must first check to see if the function property exists before trying to invoke it. In the last example, not supplying an onNewColor
function property would lead to a JavaScript error because the component will try to invoke an undefined value.
This can be avoided by first checking for the existence of the function property:
if
(
this
.
props
.
onNewColor
)
{
this
.
props
.
onNewColor
(
_title
.
value
,
_color
.
value
)
}
A better solution is to define the function property in the component’s propTypes
and defaultProps
:
AddColorForm
.
propTypes
=
{
onNewColor
:
PropTypes
.
func
}
AddColorForm
.
defaultProps
=
{
onNewColor
:
f
=>
f
}
Now when the property supplied is some type other than function, React will complain. If the onNewColor
property is not supplied, it will default to this dummy function, f=>f
. This is simply a placeholder function that returns the first argument sent to it. Although this placeholder function doesn’t do anything, it can be invoked by JavaScript without causing errors.
Refs can also be used in stateless functional components. These components do not have this
, so it’s not possible to use this.refs
. Instead of using string attributes, we will set the refs using a function. The function will pass us the input instance as an argument. We can capture that instance and save it to a local variable.
Let’s refactor AddColorForm
as a stateless functional component:
const
AddColorForm
=
(
{
onNewColor
=
f
=>
f
}
)
=>
{
let
_title
,
_color
const
submit
=
e
=>
{
e
.
preventDefault
(
)
onNewColor
(
_title
.
value
,
_color
.
value
)
_title
.
value
=
''
_color
.
value
=
'#000000'
_title
.
focus
(
)
}
return
(
<
form
onSubmit
=
{
submit
}
>
<
input
ref
=
{
input
=>
_title
=
input
}
type
=
"text"
placeholder
=
"color title..."
required
/
>
<
input
ref
=
{
input
=>
_color
=
input
}
type
=
"color"
required
/
>
<
button
>
ADD
<
/
b
u
t
t
o
n
>
<
/
f
o
r
m
>
)
}
In this stateless functional component, refs are set with a callback function instead of a string. The callback function passes the element’s instance as an argument. This instance can be captured and saved into a local variable like _title
or _color
. Once we’ve saved the refs to local variables, they are easily accessed when the form is submitted.
Thus far we’ve only used properties to handle data in React components. Properties are immutable. Once rendered, a component’s properties do not change. In order for our UI to change, we would need some other mechanism that can rerender the component tree with new properties. React state is a built-in option for managing data that will change within a component. When application state changes, the UI is rerendered to reflect those changes.
Users interact with applications. They navigate, search, filter, select, add, update, and delete. When a user interacts with an application, the state of that application changes, and those changes are reflected back to the user in the UI. Screens and menus appear and disappear. Visible content changes. Indicators light up or are turned off. In React, the UI is a reflection of application state.
State can be expressed in React components with a single JavaScript object. When the state of a component changes, the component renders a new UI that reflects those changes. What can be more functional than that? Given some data, a React component will represent that data as the UI. Given a change to that data, React will update the UI as efficiently as possible to reflect that change.
Let’s take a look at how we can incorporate state within our React components.
State represents data that we may wish to change within a component. To demonstrate this, we will take a look at a StarRating
component (Figure 6-7).
The StarRating
component requires two critical pieces of data: the total number of stars to display, and the rating, or the number of stars to highlight.
We’ll need a clickable Star
component that has a selected
property. A stateless functional component can be used for each star:
const
Star
=
({
selected
=
false
,
onClick
=
f
=>
f
})
=>
<
div
className
=
{(
selected
)
?
"star selected"
:
"star"
}
onClick
=
{
onClick
}
>
<
/div>
Star
.
propTypes
=
{
selected
:
PropTypes
.
bool
,
onClick
:
PropTypes
.
func
}
Every Star
element will consist of a div
that includes the class 'star'
. If the star is selected, it will additionally add the class 'selected'
. This component also has an optional onClick
property. When a user clicks on any star div
, the onClick
property will be invoked. This will tell the parent component, the StarRating
, that a Star
has been clicked.
The Star
is a stateless functional component. It says it right in the name: you cannot use state in a stateless functional component. Stateless functional components are meant to be the children of more complex, stateful components. It’s a good idea to try to keep as many of your components as possible stateless.
Our StarRating
component uses CSS to construct and display a star. Specifically, using a clip path, we can clip the area of our div
to look like a star. The clip path is collection of points that make up a polygon:
.star
{
cursor
:
pointer
;
height
:
25px
;
width
:
25px
;
margin
:
2px
;
float
:
left
;
background-color
:
grey
;
clip-path
:
polygon
(
50%
0%
,
63%
38%
,
100%
38%
,
69%
59%
,
82%
100%
,
50%
75%
,
18%
100%
,
31%
59%
,
0%
38%
,
37%
38%
);
}
.star.selected
{
background-color
:
red
;
}
A regular star has a background color of grey, but a selected star will have a background color of red.
Now that we have a Star
, we can use it to create a StarRating
. StarRating
will obtain the total number of stars to display from the component’s properties. The rating, the value that the user can change, will be stored in the state.
First, let’s look at how to incorporate state into a component defined with createClass
:
const
StarRating
=
createClass
(
{
displayName
:
'StarRating'
,
propTypes
:
{
totalStars
:
PropTypes
.
number
}
,
getDefaultProps
(
)
{
return
{
totalStars
:
5
}
}
,
getInitialState
(
)
{
return
{
starsSelected
:
0
}
}
,
change
(
starsSelected
)
{
this
.
setState
(
{
starsSelected
}
)
}
,
render
(
)
{
const
{
totalStars
}
=
this
.
props
const
{
starsSelected
}
=
this
.
state
return
(
<
div
className
=
"star-rating"
>
{
[
...
Array
(
totalStars
)
]
.
map
(
(
n
,
i
)
=>
<
Star
key
=
{
i
}
selected
=
{
i
<
starsSelected
}
onClick
=
{
(
)
=>
this
.
change
(
i
+
1
)
}
/
>
)
}
<
p
>
{
starsSelected
}
of
{
totalStars
}
stars
<
/
p
>
<
/
d
i
v
>
)
}
}
)
When using createClass
, state can be initialized by adding getInitialState
to the component configuration and returning a JavaScript object that initially sets the state variable, starsSelected
to 0
.
When the component renders, totalStars
is obtained from the component’s properties and used to render a specific number of Star
elements. Specifically, the spread operator is used with the Array
constructor to initialize a new array of a specific length that is mapped to Star
elements.
The state variable starsSelected
is destructured from this.state
when the component renders. It is used to display the rating as text in a paragraph element. It is also used to calculate the number of selected stars to display. Each Star
element obtains its selected
property by comparing its index to the number of stars that are selected. If three stars are selected, the first three Star
elements will set their selected
property to true
and any remaining stars will have a selected
property of false
.
Finally, when a user clicks a single star, the index of that specific Star
element is incremented and sent to the change
function. This value is incremented because it is assumed that the first star will have a rating of 1 even though it has an index of 0.
Initializing state in an ES6 component class is slightly different than using createClass
. In these classes, state can be initialized in the constructor:
class
StarRating
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
starsSelected
:
0
}
this
.
change
=
this
.
change
.
bind
(
this
)
}
change
(
starsSelected
)
{
this
.
setState
(
{
starsSelected
}
)
}
render
(
)
{
const
{
totalStars
}
=
this
.
props
const
{
starsSelected
}
=
this
.
state
return
(
<
div
className
=
"star-rating"
>
{
[
...
Array
(
totalStars
)
]
.
map
(
(
n
,
i
)
=>
<
Star
key
=
{
i
}
selected
=
{
i
<
starsSelected
}
onClick
=
{
(
)
=>
this
.
change
(
i
+
1
)
}
/
>
)
}
<
p
>
{
starsSelected
}
of
{
totalStars
}
stars
<
/
p
>
<
/
d
i
v
>
)
}
}
StarRating
.
propTypes
=
{
totalStars
:
PropTypes
.
number
}
StarRating
.
defaultProps
=
{
totalStars
:
5
}
When an ES6 component is mounted, its constructor is invoked with the properties injected as the first argument. Those properties are, in turn, sent to the superclass by invoking super
. In this case, the superclass is React.Component
. Invoking super
initializes the component instance, and React.Component
decorates that instance with functionality that includes state management. After invoking super
, we can initialize our component’s state variables.
Once the state is initialized, it operates as it does in createClass
components. State can only be changed by calling this.setState
, which updates specific parts of the state object. After every setState
call, the render
function is called, updating the state with the new UI.
We can initialize our state values using incoming properties. There are only a few necessary cases for this pattern. The most common case for this is when we create a reusable component that we would like to use across applications in different component trees.
When using createClass
, a good way to initialize state variables based on incoming properties is to add a method called componentWillMount
. This method is invoked once when the component mounts, and you can call this.setState()
from this method. It also has access to this.props
, so you can use values from this.props
to help you initialize state:
const
StarRating
=
createClass
(
{
displayName
:
'StarRating'
,
propTypes
:
{
totalStars
:
PropTypes
.
number
}
,
getDefaultProps
(
)
{
return
{
totalStars
:
5
}
}
,
getInitialState
(
)
{
return
{
starsSelected
:
0
}
}
,
componentWillMount
(
)
{
const
{
starsSelected
}
=
this
.
props
if
(
starsSelected
)
{
this
.
setState
(
{
starsSelected
}
)
}
}
,
change
(
starsSelected
)
{
this
.
setState
(
{
starsSelected
}
)
}
,
render
(
)
{
const
{
totalStars
}
=
this
.
props
const
{
starsSelected
}
=
this
.
state
return
(
<
div
className
=
"star-rating"
>
{
[
...
Array
(
totalStars
)
]
.
map
(
(
n
,
i
)
=>
<
Star
key
=
{
i
}
selected
=
{
i
<
starsSelected
}
onClick
=
{
(
)
=>
this
.
change
(
i
+
1
)
}
/
>
)
}
<
p
>
{
starsSelected
}
of
{
totalStars
}
stars
<
/
p
>
<
/
d
i
v
>
)
}
}
)
render
(
<
StarRating
totalStars
=
{
7
}
starsSelected
=
{
3
}
/
>
,
document
.
getElementById
(
'react-container'
)
)
componentWillMount
is a part of the component lifecycle. It can be used to help you initialize state based on property values in components created with createClass
or ES6 class components. We will dive deeper into the component lifecycle in the next chapter.
There is an easier way to initialize state within an ES6 class component. The constructor receives properties as an argument, so you can simply use the props
argument passed to the constructor:
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
starsSelected
:
props
.
starsSelected
||
0
}
this
.
change
=
this
.
change
.
bind
(
this
)
}
For the most part, you’ll want to avoid setting state variables from properties. Only use these patterns when they are absolutely required. You should find this goal easy to accomplish because when working with React components, you want to limit the number of components that have state.3
When initializing state variables from component properties, you may need to reinitialize component state when a parent component changes those properties. The componentWillRecieveProps
lifecycle method can be used to solve this issue. Chapter 7 goes into greater detail on this issue and the available methods of the component lifecycle.
All of your React components can have their own state, but should they? The joy of using React does not come from chasing down state variables all over your application. The joy of using React comes from building scalable applications that are easy to understand. The most important thing that you can do to make your application easy to understand is limit the number of components that use state as much as possible.
In many React applications, it is possible to group all state data in the root component. State data can be passed down the component tree via properties, and data can be passed back up the tree to the root via two-way function binding. The result is that all of the state for your entire application exists in one place. This is often referred to as having a “single source of truth.”4
Next, we will look at how to architect presentation layers where all of the state is stored in one place, the root component.
The color organizer allows users to add, name, rate, and remove colors in their customized lists. The entire state of the color organizer can be represented with a single array:
{
colors
:
[
{
"id"
:
"0175d1f0-a8c6-41bf-8d02-df5734d829a4"
,
"title"
:
"ocean at dusk"
,
"color"
:
"#00c4e2"
,
"rating"
:
5
},
{
"id"
:
"83c7ba2f-7392-4d7d-9e23-35adbe186046"
,
"title"
:
"lawn"
,
"color"
:
"#26ac56"
,
"rating"
:
3
},
{
"id"
:
"a11e3995-b0bd-4d58-8c48-5e49ae7f7f23"
,
"title"
:
"bright red"
,
"color"
:
"#ff0000"
,
"rating"
:
0
}
]
}
The array tells us that we need to display three colors: ocean at dusk, lawn, and bright red (Figure 6-8). It gives us the colors’ hex values and the current rating for each color in the display. It also provides a way to uniquely identify each color.
This state data will drive our application. It will be used to construct the UI every time this object changes. When users add or remove colors, they will be added to or removed from this array. When users rate colors, their ratings will change in the array.
Earlier in this chapter, we created a StarRating
component that saved the rating in the state. In the color organizer, the rating is stored in each color object. It makes more sense to treat the StarRating
as a presentational component5 and declare it as a stateless functional component. Presentational components are only concerned with how things look in the application. They only render DOM elements or other presentational components. All data is sent to these components via properties and passed out of these components via callback functions.
In order to make the StarRating
component purely presentational, we need to remove state. Presentational components only use props. Since we are removing state from this component, when a user changes the rating, that data will be passed out of this component via a callback function:
const
StarRating
=
(
{
starsSelected
=
0
,
totalStars
=
5
,
onRate
=
f
=>
f
}
)
=>
<
div
className
=
"star-rating"
>
{
[
...
Array
(
totalStars
)
]
.
map
(
(
n
,
i
)
=>
<
Star
key
=
{
i
}
selected
=
{
i
<
starsSelected
}
onClick
=
{
(
)
=>
onRate
(
i
+
1
)
}
/
>
)
}
<
p
>
{
starsSelected
}
of
{
totalStars
}
stars
<
/
p
>
<
/
d
i
v
>
First, starsSelected
is no longer a state variable; it is a property. Second, an onRate
callback property has been added to this component. Instead of calling setState
when the user changes the rating, this component now invokes onRate
and sends the rating as an argument.
You may need to create stateful UI components for distribution and reuse across many different applications. It is not absolutely required that you remove every last state variable from components that are only used for presentation. It is a good rule to follow, but sometimes it may make sense to keep state in a presentation component.
Restricting state to a single location, the root component, means that all of the data must be passed down to child components as properties (Figure 6-9).
In the color organizer, state consists of an array of colors that is declared in the App
component. Those colors are passed down to the ColorList
component as a property:
class
App
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
colors
:
[
]
}
}
render
(
)
{
const
{
colors
}
=
this
.
state
return
(
<
div
className
=
"app"
>
<
AddColorForm
/
>
<
ColorList
colors
=
{
colors
}
/
>
<
/
d
i
v
>
)
}
}
Initially the colors
array is empty, so the ColorList
component will display a message instead of each color. When there are colors in the array, data for each individual color is passed to the Color
component as properties:
const
ColorList
=
(
{
colors
=
[
]
}
)
=>
<
div
className
=
"color-list"
>
{
(
colors
.
length
===
0
)
?
<
p
>
No
Colors
Listed
.
(
Add
a
Color
)
<
/
p
>
:
colors
.
map
(
color
=>
<
Color
key
=
{
color
.
id
}
{
...
color
}
/
>
)
}
<
/
d
i
v
>
Now the Color
component can display the color’s title and hex value and pass the color’s rating down to the StarRating
component as a property:
const
Color
=
(
{
title
,
color
,
rating
=
0
}
)
=>
<
section
className
=
"color"
>
<
h1
>
{
title
}
<
/
h
1
>
<
div
className
=
"color"
style
=
{
{
backgroundColor
:
color
}
}
>
<
/
d
i
v
>
<
div
>
<
StarRating
starsSelected
=
{
rating
}
/
>
<
/
d
i
v
>
<
/
s
e
c
t
i
o
n
>
The number of starsSelected
in the star rating comes from each color’s rating. All of the state data for every color has been passed down the tree to child components as properties. When there is a change to the data in the root component, React will change the UI as efficiently as possible to reflect the new state.
State in the color organizer can only be updated by calling setState
from the App
component. If users initiate any changes from the UI, their input will need to be passed back up the component tree to the App
component in order to update the state (Figure 6-10). This can be accomplished through the use of callback function properties.
In order to add new colors, we need a way to uniquely identify each color. This identifier will be used to locate colors within the state array. We can use the uuid
library to create absolutely unique IDs:
npm install uuid --save
All new colors will be added to the color organizer from the AddColorForm
component that we constructed in “Refs”. That component has an optional callback function property called onNewColor
. When the user adds a new color and submits the form, the onNewColor
callback function is invoked with the new title and color hex value obtained from the user:
import
{
Component
}
from
'react'
import
{
v4
}
from
'uuid'
import
AddColorForm
from
'./AddColorForm'
import
ColorList
from
'./ColorList'
export
class
App
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
colors
:
[
]
}
this
.
addColor
=
this
.
addColor
.
bind
(
this
)
}
addColor
(
title
,
color
)
{
const
colors
=
[
...
this
.
state
.
colors
,
{
id
:
v4
(
)
,
title
,
color
,
rating
:
0
}
]
this
.
setState
(
{
colors
}
)
}
render
(
)
{
const
{
addColor
}
=
this
const
{
colors
}
=
this
.
state
return
(
<
div
className
=
"app"
>
<
AddColorForm
onNewColor
=
{
addColor
}
/
>
<
ColorList
colors
=
{
colors
}
/
>
<
/
d
i
v
>
)
}
}
All new colors can be added from the addColor
method in the App
component. This function is bound to the component in the constructor, which means that it has access to this.state
and this.setState
.
New colors are added by concatenating the current colors
array with a new color object. The ID for the new color object is set using uuid
’s v4
function. This creates a unique identifier for each color. The title and color are passed to the addColor
method from the AddColorForm
component. Finally, the initial value for each color’s rating will be 0
.
When the user adds a color with the AddColorForm
component, the addColor
method updates the state with a new list of colors. Once the state has been updated, the App
component rerenders the component tree with the new list of colors. The render
method is invoked after every setState
call. The new data is passed down the tree as properties and is used to construct the UI.
If the user wishes to rate or remove a color, we need to collect information about that color. Each color will have a remove button: if the user clicks the remove button, we’ll know they wish to remove that color. Also, if the user changes the color’s rating with the StarRating
component, we want to change the rating of that color:
const
Color
=
(
{
title
,
color
,
rating
=
0
,
onRemove
=
f
=>
f
,
onRate
=
f
=>
f
}
)
=>
<
section
className
=
"color"
>
<
h1
>
{
title
}
<
/
h
1
>
<
button
onClick
=
{
onRemove
}
>
X
<
/
b
u
t
t
o
n
>
<
div
className
=
"color"
style
=
{
{
backgroundColor
:
color
}
}
>
<
/
d
i
v
>
<
div
>
<
StarRating
starsSelected
=
{
rating
}
onRate
=
{
onRate
}
/
>
<
/
d
i
v
>
<
/
s
e
c
t
i
o
n
>
The information that will change in this app is stored in the list of colors. Therefore, onRemove
and onRate
callback properties will have to be added to each color to pass those events back up the tree. The Color
component will also have onRate
and onRemove
callback function properties. When colors are rated or removed, the ColorList
component will need to notify its parent, the App
component, that the color should be rated or removed:
const
ColorList
=
(
{
colors
=
[
]
,
onRate
=
f
=>
f
,
onRemove
=
f
=>
f
}
)
=>
<
div
className
=
"color-list"
>
{
(
colors
.
length
===
0
)
?
<
p
>
No
Colors
Listed
.
(
Add
a
Color
)
<
/
p
>
:
colors
.
map
(
color
=>
<
Color
key
=
{
color
.
id
}
{
...
color
}
onRate
=
{
(
rating
)
=>
onRate
(
color
.
id
,
rating
)
}
onRemove
=
{
(
)
=>
onRemove
(
color
.
id
)
}
/
>
)
}
<
/
d
i
v
>
The ColorList
component will invoke onRate
if any colors are rated and onRemove
if any colors are removed. This component manages the collection of colors by mapping them to individual Color
components. When individual colors are rated or removed the ColorList
identifies which color was rated or removed and passes that info to its parent via callback function properties.
ColorList
’s parent is App
. In the App
component, rateColor
and removeColor
methods can be added and bound to the component instance in the constructor. Any time a color needs to be rated or removed, these methods will update the state. They are added to the ColorList
component as callback function properties:
class
App
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
colors
:
[
]
}
this
.
addColor
=
this
.
addColor
.
bind
(
this
)
this
.
rateColor
=
this
.
rateColor
.
bind
(
this
)
this
.
removeColor
=
this
.
removeColor
.
bind
(
this
)
}
addColor
(
title
,
color
)
{
const
colors
=
[
...
this
.
state
.
colors
,
{
id
:
v4
(
)
,
title
,
color
,
rating
:
0
}
]
this
.
setState
(
{
colors
}
)
}
rateColor
(
id
,
rating
)
{
const
colors
=
this
.
state
.
colors
.
map
(
color
=>
(
color
.
id
!==
id
)
?
color
:
{
...
color
,
rating
}
)
this
.
setState
(
{
colors
}
)
}
removeColor
(
id
)
{
const
colors
=
this
.
state
.
colors
.
filter
(
color
=>
color
.
id
!==
id
)
this
.
setState
(
{
colors
}
)
}
render
(
)
{
const
{
addColor
,
rateColor
,
removeColor
}
=
this
const
{
colors
}
=
this
.
state
return
(
<
div
className
=
"app"
>
<
AddColorForm
onNewColor
=
{
addColor
}
/
>
<
ColorList
colors
=
{
colors
}
onRate
=
{
rateColor
}
onRemove
=
{
removeColor
}
/
>
<
/
d
i
v
>
)
}
}
Both rateColor
and removeColor
expect the ID of the color to rate or remove. The ID is captured in the ColorList
component and passed as an argument to rateColor
or removeColor
. The rateColor
method finds the color to rate and changes its rating in the state. The removeColor
method uses Array.filter
to create a new state array without the removed color.
Once setState
is called, the UI is rerendered with the new state data. All data that changes in this app is managed from a single component, App
. This approach makes it much easier to understand what data the application uses to create state and how that data will change.
React components are quite robust. They provide us with a clean way to manage and validate properties, communicate with child elements, and manage state data from within a component. These features make it possible to construct beautifully scalable presentation layers.
We have mentioned many times that state is for data that changes. You can also use state to cache data in your application. For instance, if you had a list of records that the user could search, the records list could be stored in state until they are searched.
Reducing state to root components is often recommended. You will encounter this approach in many React applications. Once your application reaches a certain size, two-way data binding and explicitly passing properties can become quite a nuisance. The Flux design pattern and Flux libraries like Redux can be used to manage state and reduce boilerplate in these situations.
React is a relatively small library, and thus far we’ve reviewed much of its functionality. The major features of React components that we have yet to discuss include the component lifecycle and higher-order components, which we will cover in the next chapter.
1 React Docs, “Default Prop Values”
2 Pete Hunt, “Thinking in React”.
3 React Docs, “Lifting State Up”.
4 Paul Hudson, “State and the Single Source of Truth”, Chapter 12 of Hacking with React.
5 Dan Abramov, “Presentational and Container Components”, Medium, March 23, 2015.