So far we’ve learned how to mount and compose components to create application presentation layers with React. It is possible to build quite a few applications using only the React component’s render
method. However, the world of JavaScript is complex. There is asynchronicity everywhere. We have latency to deal with when we load our data. We have delays to work with when we create animations. It is highly likely that you have preferred JavaScript libraries to help you navigate the complexity of real-world JavaScript.
Before we can enhance our applications with third-party JavaScript libraries or backend data requests, we must first understand how to work with the component lifecycle: a series of methods that can be invoked every time we mount or update a component.
We will start this chapter by exploring the component lifecycle. After we introduce the lifecycle, we will review how we can use it to load data, incorporate third-party JavaScript, and even improve our component’s performance. Next, we will explore how to reuse functionality across our applications with higher-order components. We will wrap up this chapter by looking at alternative application architectures that manage state entirely outside of React.
The component lifecycle consists of methods that are invoked in series when a component is mounted or updated. These methods are invoked either before or after the component renders the UI. In fact, the render
method itself is a part of the component lifecycle. There are two primary lifecycles: the mounting lifecycle and the updating lifecycle.
The mounting lifecycle consists of methods that are invoked when a component is mounted or unmounted. In other words, these methods allow you to initially set up state, make API calls, start and stop timers, manipulate the rendered DOM, initialize third-party libraries, and more. These methods allow you to incorporate JavaScript to assist in the initialization and destruction of a component.
The mounting lifecycle is slightly different depending upon whether you use ES6 class syntax or React.createClass
to create components. When you use createClass
, getDefaultProps
is invoked first to obtain the component’s properties. Next, getInitialState
is invoked to initialize the state.
ES6 classes do not have these methods. Instead, default props are obtained and sent to the constructor as an argument. The constructor is where the state is initialized. Both ES6 class constructors and getInitialState
have access to the properties and, if required, can use them to help define the initial state.
Table 7-1 lists the methods of the component mounting lifecycle.
ES6 class | React.createClass() |
---|---|
getDefaultProps() |
|
constructor(props) |
getInitialState() |
componentWillMount() |
componentWillMount() |
render() |
render() |
componentDidMount() |
componentDidMount() |
componentWillUnmount() |
componentWillUnmount() |
Technically, the constructor is not a lifecycle method. We include it because it is used for component initialization (this is where the state is initialized). Also, the constructor is always the first function invoked when a component is mounted.
Once the properties are obtained and state is initialized, the componentWillMount
method is invoked. This method is invoked before the DOM is rendered and can be used to initialize third-party libraries, start animations, request data, or perform any additional setup that may be required before a component is rendered. It is possible to invoke setState
from this method to change the component state just before the component is initially rendered.
Let’s use the componentWillMount
method to initialize a request for some members. When we get a successful response, we will update the state. Remember the getFakeMembers
promise that we created in Chapter 2? We will use that to load a random list of members from randomuser.me:
const
getFakeMembers
=
count
=>
new
Promise
((
resolves
,
rejects
)
=>
{
const
api
=
`https://api.randomuser.me/?nat=US&results=
${
count
}
`
const
request
=
new
XMLHttpRequest
()
request
.
open
(
'GET'
,
api
)
request
.
onload
=
()
=>
(
request
.
status
==
200
)
?
resolves
(
JSON
.
parse
(
request
.
response
).
results
)
:
reject
(
Error
(
request
.
statusText
))
request
.
onerror
=
err
=>
rejects
(
err
)
request
.
send
()
})
We will use this promise in the componentWillMount
method in a MemberList
component. This component will use a Member
component to display each user’s picture, name, email address, and location:
const
Member
=
(
{
,
picture
,
name
,
location
}
)
=>
<
div
className
=
"member"
>
<
img
src
=
{
picture
.
thumbnail
}
alt
=
""
/
>
<
h1
>
{
name
.
first
}
{
name
.
last
}
<
/
h
1
>
<
p
>
<
a
href
=
{
"mailto:"
+
}
>
{
}
<
/
a
>
<
/
p
>
<
p
>
{
location
.
city
}
,
{
location
.
state
}
<
/
p
>
<
/
d
i
v
>
class
MemberList
extends
Component
{
constructor
(
)
{
super
(
)
this
.
state
=
{
members
:
[
]
,
loading
:
false
,
error
:
null
}
}
componentWillMount
(
)
{
this
.
setState
(
{
loading
:
true
}
)
getFakeMembers
(
this
.
props
.
count
)
.
then
(
members
=>
{
this
.
setState
(
{
members
,
loading
:
false
}
)
}
,
error
=>
{
this
.
setState
(
{
error
,
loading
:
false
}
)
}
)
}
componentWillUpdate
(
)
{
console
.
log
(
'updating lifecycle'
)
}
render
(
)
{
const
{
members
,
loading
,
error
}
=
this
.
state
return
(
<
div
className
=
"member-list"
>
{
(
loading
)
?
<
span
>
Loading
Members
<
/
s
p
a
n
>
:
(
members
.
length
)
?
members
.
map
(
(
user
,
i
)
=>
<
Member
key
=
{
i
}
{
...
user
}
/
>
)
:
<
span
>
0
members
loaded
...
<
/
s
p
a
n
>
}
{
(
error
)
?
<
p
>
Error
Loading
Members
:
error
<
/
p
>
:
"
"
}
<
/
d
i
v
>
)
}
}
Initially, when the component is mounted, MemberList
has an empty array for members
and loading
is false
. In the componentWillMount
method, the state is changed to reflect the fact that a request was made to load some users. Next, while waiting for the request to complete, the component is rendered. Because loading
is now true
, a message will be displayed alerting the user to the latency. When the promise passes or fails, the loading state is returned to false
and either the members have been loaded or an error has been returned. Calling setState
at this point will rerender our UI with either some members or an error.
Calling setState
before the component has rendered will not kick off the updating lifecycle. Calling setState
after the component has been rendered will kick off the updating lifecycle. If you call setState
inside an asynchronous callback defined within the componentWillMount
method, it will be invoked after the component has rendered and will trigger the updating lifecycle.
The other methods of the component mounting lifecycle include componentDidMount
and componentWillUnmount
. componentDidMount
is invoked just after the component has rendered, and componentWillUnmount
is invoked just before the component is unmounted.
componentDidMount
is another good place to make API requests. This method is invoked after the component has rendered, so any setState
calls from this method will kick off the updating lifecycle and rerender the component.
componentDidMount
is also a good place to initialize any third-party JavaScript that requires a DOM. For instance, you may want to incorporate a drag-and-drop library or a library that handles touch events. Typically, these libraries require a DOM before they can be initialized.
Another good use for componentDidMount
is to start background processes like intervals or timers. Any processes started in componentDidMount
or componentWillMount
can be cleaned up in componentWillUnmount
. You don’t want to leave background processes running when they are not needed.
Components are unmounted when their parents remove them or they have been unmounted with the unmountComponentAtNode
function found in react-dom
. This method is used to unmount the root component. When a root component is unmounted, its children are unmounted first.
Let’s take a look at a clock example. When the Clock
component has mounted, a timer will be started. When the user clicks on the close button, the clock will be unmounted with unmountComponentAtNode
and the timer stopped:
import
React
from
'react'
import
{
render
,
unmountComponentAtNode
}
from
'react-dom'
import
{
getClockTime
}
from
'./lib'
const
{
Component
}
=
React
const
target
=
document
.
getElementById
(
'react-container'
)
class
Clock
extends
Component
{
constructor
(
)
{
super
(
)
this
.
state
=
getClockTime
(
)
}
componentDidMount
(
)
{
console
.
log
(
"Starting Clock"
)
this
.
ticking
=
setInterval
(
(
)
=>
this
.
setState
(
getClockTime
(
)
)
,
1000
)
}
componentWillUnmount
(
)
{
clearInterval
(
this
.
ticking
)
console
.
log
(
"Stopping Clock"
)
}
render
(
)
{
const
{
hours
,
minutes
,
seconds
,
ampm
}
=
this
.
state
return
(
<
div
className
=
"clock"
>
<
span
>
{
hours
}
<
/
s
p
a
n
>
<
span
>
:
<
/
s
p
a
n
>
<
span
>
{
minutes
}
<
/
s
p
a
n
>
<
span
>
:
<
/
s
p
a
n
>
<
span
>
{
seconds
}
<
/
s
p
a
n
>
<
span
>
{
ampm
}
<
/
s
p
a
n
>
<
button
onClick
=
{
this
.
props
.
onClose
}
>
x
<
/
b
u
t
t
o
n
>
<
/
d
i
v
>
)
}
}
render
(
<
Clock
onClose
=
{
(
)
=>
unmountComponentAtNode
(
target
)
}
/
>
,
target
)
In Chapter 3, we created a serializeTime
function that abstracts civilian time with leading zeros from the data object. Every time serializeTime
is invoked, the current time is returned in an object that contains hours, minutes, seconds, and the a.m. or p.m. indicator. Initially, we call serializeTime
to get the initial state for our clock.
After the component has mounted, we start an interval called ticking
. It invokes setState
with a new time every second. The UI for the clock changes its value to the updated time every second.
When the close button is clicked, the Clock
component is unmounted. Just before the clock is removed from the DOM, the ticking
interval is cleared so that it no longer runs in the background.
The updating lifecycle is a series of methods that are invoked when a component’s state changes or when new properties are received from the parent. This lifecycle can be used to incorporate JavaScript before the component updates or to interact with the DOM after the update. Additionally, it can be used to improve the performance of an application because it gives you the ability to cancel unnecessary updates.
The updating lifecycle kicks off every time setState
is called. Calling setState
within the updating lifecycle will cause an infinite recursive loop that results in a stack overflow error. Therefore, setState
can only be called in componentWillReceiveProps
, which allows the component to update state when its properties are updated.
The updating lifecycle methods include:
componentWillReceiveProps(nextProps)
Only invoked if new properties have been passed to the component. This is the only method where setState
can be called.
shouldComponentUpdate(nextProps, nextState)
The update lifecycle’s gatekeeper—a predicate that can call off the update. This method can be used to improve performance by only allowing necessary updates.
componentWillUpdate(nextProps, nextState)
Invoked just before the component updates. Similar to componentWillMount
, only it is invoked before each update occurs.
componentDidUpdate(prevProps, prevState)
Invoked just after the update takes place, after the call to render
. Similar to componentDidMount
, but it is invoked after each update.
Let’s modify the color organizer application that we created in the last chapter. Specifically, we’ll add some updating lifecycle functions to the Color
component that will allow us to see how the updating lifecycle works. Let’s assume that we already have four colors in the state array: Ocean Blue, Tomato, Lawn, and Party Pink. First, we will use the componentWillMount
method to initialize color objects with a style, and set all four Color
elements to have grey backgrounds:
import
{
Star
,
StarRating
}
from
'../components'
export
class
Color
extends
Component
{
componentWillMount
(
)
{
this
.
style
=
{
backgroundColor
:
"#CCC"
}
}
render
(
)
{
const
{
title
,
rating
,
color
,
onRate
}
=
this
.
props
return
<
section
className
=
"color"
style
=
{
this
.
style
}
>
<
h1
ref
=
"title"
>
{
title
}
<
/
h
1
>
<
div
className
=
"color"
style
=
{
{
backgroundColor
:
color
}
}
>
<
/
d
i
v
>
<
StarRating
starsSelected
=
{
rating
}
onRate
=
{
onRate
}
/
>
<
/
s
e
c
t
i
o
n
>
}
}
Color
.
propTypes
=
{
title
:
PropTypes
.
string
,
rating
:
PropTypes
.
number
,
color
:
PropTypes
.
string
,
onRate
:
PropTypes
.
func
}
Color
.
defaultProps
=
{
title
:
undefined
,
rating
:
0
,
color
:
"#000000"
,
onRate
:
f
=>
f
}
When the color list originally mounts, each color background will be grey (Figure 7-1).
We can add componentWillUpdate
to the Color
component in order to remove the grey background from each color just before the color updates:
componentWillMount
(
)
{
this
.
style
=
{
backgroundColor
:
"#CCC"
}
}
componentWillUpdate
(
)
{
this
.
style
=
null
}
Adding these lifecycle functions allows us to see when a component has mounted and when that component is updating. Initially, mounted components will have a grey background. Once each color is updated, the background will return to white.
If you run this code and rate any color, you will notice that all four colors update even though you have only changed the rating of a single color (see Figure 7-2).
Here, changing the rating of Ocean Blue from three to four stars caused all four colors to update because when the parent, the ColorList
, updates state, it rerenders each Color
component. Components that are rerendered are not re-mounted; if they are already there, an update occurs instead. When a component is updated, all of its children are also updated. When a single color is rated, all four colors are updated, all four StarRating
components are updated, and all five stars on each component are updated.
We can improve the performance of our application by preventing colors from being updated when their property values have not changed. Adding the lifecycle function shouldComponentUpdate
prevents unnecessary updates from occurring. This method returns either true
or false
(true
when the component should update and false
when updating should be skipped):
componentWillMount
(
)
{
this
.
style
=
{
backgroundColor
:
"#CCC"
}
}
shouldComponentUpdate
(
nextProps
)
{
const
{
rating
}
=
this
.
props
return
rating
!==
nextProps
.
rating
}
componentWillUpdate
(
)
{
this
.
style
=
null
}
The shouldComponentUpdate
method can compare the new properties with the old ones. The new properties are passed to this method as an argument, the old properties are still the current props, and the component has not updated. If the rating is the same in the current properties and the new ones, there is no need for the color to update. If the color does not update, none of its children will update either. When the rating does not change, the entire component tree under each Color
will not update.
This can be demonstrated by running this code and updating any of the colors. The componentWillUpdate
method is only called if the component is going to update. It comes after shouldComponentUpdate
in the lifecycle. The backgrounds will stay grey until the Color
components are updated by changing their ratings (Figure 7-3).
If the shouldComponentUpdate
method returns true
, the rest of the updating lifecycle will get to it. The rest of the lifecycle functions also receive the new props and new state as arguments. (The componentDidUpdate
method receives the previous props and the previous state because once this method is reached, the update already has occurred and the props have been changed.)
Let’s log a message after the component updates. In the componentDidUpdate
function, we’ll compare the current properties to the old ones to see if the rating got better or worse:
componentWillMount
(
)
{
this
.
style
=
{
backgroundColor
:
"#CCC"
}
}
shouldComponentUpdate
(
nextProps
)
{
const
{
rating
}
=
this
.
props
return
rating
!==
nextProps
.
rating
}
componentWillUpdate
(
)
{
this
.
style
=
null
}
componentDidUpdate
(
prevProps
)
{
const
{
title
,
rating
}
=
this
.
props
const
status
=
(
rating
>
prevProps
.
rating
)
?
'better'
:
'worse'
console
.
log
(
`
${
title
}
is getting
${
status
}
`
)
}
The updating lifecycle methods componentWillUpdate
and componentDidUpdate
are great places to interact with DOM elements before or after updates. In this next sample, the updating process will be paused with an alert in componentWillUpdate
:
componentWillMount
(
)
{
this
.
style
=
{
backgroundColor
:
"#CCC"
}
}
shouldComponentUpdate
(
nextProps
)
{
return
this
.
props
.
rating
!==
nextProps
.
rating
}
componentWillUpdate
(
nextProps
)
{
const
{
title
,
rating
}
=
this
.
props
this
.
style
=
null
this
.
refs
.
title
.
style
.
backgroundColor
=
"red"
this
.
refs
.
title
.
style
.
color
=
"white"
alert
(
`
${
title
}
: rating
${
rating
}
->
${
nextProps
.
rating
}
`
)
}
componentDidUpdate
(
prevProps
)
{
const
{
title
,
rating
}
=
this
.
props
const
status
=
(
rating
>
prevProps
.
rating
)
?
'better'
:
'worse'
this
.
refs
.
title
.
style
.
backgroundColor
=
""
this
.
refs
.
title
.
style
.
color
=
"black"
}
If change the rating of Tomato from two to four stars, the updating process will be paused by an alert (Figure 7-4). The current DOM element for the color’s title is given a different background and text color.
As soon as we clear the alert, the component updates and componentDidUpdate
is invoked, clearing the title’s background color (Figure 7-5).
Sometimes our components hold state that is originally set based upon properties. We can set the initial state of our component classes in the constructor or the componentWillMount
lifecycle method. When those properties change, we will need to update the state using the componentWillReceiveProps
method.
In Example 7-1, we have a parent component that holds state, HiddenMessages
. This component holds three messages in state and shows only one message at a time. When HiddenMessages
mounts, an interval is added to cycle through the messages, only displaying one at a time.
class
HiddenMessages
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
messages
:
[
"The crow crows after midnight"
,
"Bring a watch and dark clothes to the spot"
,
"Jericho Jericho Go"
]
,
showing
:
-
1
}
}
componentWillMount
(
)
{
this
.
interval
=
setInterval
(
(
)
=>
{
let
{
showing
,
messages
}
=
this
.
state
showing
=
(
++
showing
>=
messages
.
length
)
?
-
1
:
showing
this
.
setState
(
{
showing
}
)
}
,
1000
)
}
componentWillUnmount
(
)
{
clearInterval
(
this
.
interval
)
}
render
(
)
{
const
{
messages
,
showing
}
=
this
.
state
return
(
<
div
className
=
"hidden-messages"
>
{
messages
.
map
(
(
message
,
i
)
=>
<
HiddenMessage
key
=
{
i
}
hide
=
{
(
i
!==
showing
)
}
>
{
message
}
<
/
H
i
d
d
e
n
M
e
s
s
a
g
e
>
)
}
<
/
d
i
v
>
)
}
}
The HiddenMessages
component cycles through each of the messages in the state array and shows one at a time. The logic for this is set up in componentWillMount
. When the component mounts, an interval is added that updates the index for the message that should be showing. The component renders all of the messages using the HiddenMessage
component and only sets the hide
property on one of them to true
on each cycle. The rest of the properties are set to false
, and the hidden message changes every second.
Take a look at the HiddenMessage
component, the one used for each message (Example 7-2). When this component is originally mounted, the hide
property is used to determine its state. However, when the parent updates this component’s properties, nothing happens. This component will not know about it.
class
HiddenMessage
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
hidden
:
(
props
.
hide
)
?
props
.
hide
:
true
}
}
render
(
)
{
const
{
children
}
=
this
.
props
const
{
hidden
}
=
this
.
state
return
(
<
p
>
{
(
hidden
)
?
children
.
replace
(
/[a-zA-Z0-9]/g
,
"x"
)
:
children
}
<
/
p
>
)
}
}
The problem occurs when the parent component changes the hide
property. That change does not automatically cause the state of HiddenMessage
to change.
The componentWillReceiveProps
lifecycle method was created for these scenarios. It will be invoked when the properties have been changed by the parent, and those changed properties can be used to modify the state internally:
class
HiddenMessage
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
hidden
:
(
props
.
hide
)
?
props
.
hide
:
true
}
}
componentWillReceiveProps
(
nextProps
)
{
this
.
setState
(
{
hidden
:
nextProps
.
hide
}
)
}
render
(
)
{
const
{
children
}
=
this
.
props
const
{
hidden
}
=
this
.
state
return
(
<
p
>
{
(
hidden
)
?
children
.
replace
(
/[a-zA-Z0-9]/g
,
"x"
)
:
children
}
<
/
p
>
)
}
}
When the parent component, HiddenMessages
, changes the property for hide
, componentWillReceiveProps
allows us to update the state.
The component lifecycle methods give us much more control over how a component should be rendered or updated. They provide hooks for us to add functionality before or after both mounting and updating have occurred. Next, we will explore how these lifecycle methods can be used to incorporate third-party JavaScript libraries. First, however, we’ll take a brief look at the “React.Children” API.
React.Children
provides a way of working with the children of a particular component. It allows you to count, map, loopover, or convert props.children
to an array. It also allows you to verify that you are displaying a single child with React.Children.only
:
import
{
Children
,
PropTypes
}
from
'react'
import
{
render
}
from
'react-dom'
const
Display
=
(
{
ifTruthy
=
true
,
children
}
)
=>
(
ifTruthy
)
?
Children
.
only
(
children
)
:
null
const
age
=
22
render
(
<
Display
ifTruthy
=
{
age
>=
21
}
>
<
h1
>
You
can
enter
<
/
h
1
>
<
/
D
i
s
p
l
a
y
>
,
document
.
getElementById
(
'react-container'
)
)
In this example, the Display
component will display only a single child, the h1
element. If the Display
component contained multiple children, React would throw an error: “onlyChild must be passed a children with exactly one child.”
We can also use React.Children
to convert the children
property to an array. This next sample extends the Display
component to additionally handle else cases:
const
{
Children
,
PropTypes
}
=
React
const
{
render
}
=
ReactDOM
const
findChild
=
(
children
,
child
)
=>
Children
.
toArray
(
children
)
.
filter
(
c
=>
c
.
type
===
child
)
[
0
]
const
WhenTruthy
=
(
{
children
}
)
=>
Children
.
only
(
children
)
const
WhenFalsy
=
(
{
children
}
)
=>
Children
.
only
(
children
)
const
Display
=
(
{
ifTruthy
=
true
,
children
}
)
=>
(
ifTruthy
)
?
findChild
(
children
,
WhenTruthy
)
:
findChild
(
children
,
WhenFalsy
)
const
age
=
19
render
(
<
Display
ifTruthy
=
{
age
>=
21
}
>
<
WhenTruthy
>
<
h1
>
You
can
Enter
<
/
h
1
>
<
/
W
h
e
n
T
r
u
t
h
y
>
<
WhenFalsy
>
<
h1
>
Beat
it
Kid
<
/
h
1
>
<
/
W
h
e
n
F
a
l
s
y
>
<
/
D
i
s
p
l
a
y
>
,
document
.
getElementById
(
'react-container'
)
)
The Display
component will display a single child when a condition is true or another when the condition is false. To accomplish this, we create WhenTruthy
and WhenFalsy
components and use them as children in the Display
component. The findChild
function uses React.Children
to convert the children into an array. We can filter that array to locate and return an individual child by component type.
Frameworks such as Angular and jQuery come with their own tools for accessing data, rendering the UI, modeling state, handling routing, and more. React, on the other hand, is simply a library for creating views, so we may need to work with other JavaScript libraries. If we understand how the lifecycle functions operate, we can make React play nice with just about any JavaScript library.
Using jQuery with React is generally frowned upon by the community. It is possible to integrate jQuery and React, and the integration could be a good choice for learning React or migrating legacy code to React. However, applications perform much better if we incorporate smaller libraries with React, as opposed to large frameworks. Additionally, using jQuery to manipulate the DOM directly bypasses the virtual DOM, which can lead to strange errors.
In this section, we’ll incorporate a couple of different JavaScript libraries into React components. Specifically, we’ll look at ways to make API calls and visualize data with the support of other JavaScript libraries.
Fetch is a polyfill created by the WHATWG group that allows us to easily make API calls using promises. In this section we will introduce isomorphic-fetch
, a version of Fetch that works nicely with React. Let’s install isomorphic-fetch
:
npm install isomorphic-fetch --save
The component lifecycle functions provide us a place to integrate JavaScript. In this case, they are where we will make an API call. Components that make API calls have to handle latency, the delay that the user experiences while waiting for a response. We can address these issues in our state by including variables that tell the component whether a request is pending or not.
In the following example, the CountryList
component creates an ordered list of country names. Once mounted, the component makes an API call and changes the state to reflect that it is loading data. The loading state remains true
until there is a response from this API call:
import
{
Component
}
from
'react'
import
{
render
}
from
'react-dom'
import
fetch
from
'isomorphic-fetch'
class
CountryList
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
countryNames
:
[
]
,
loading
:
false
}
}
componentDidMount
(
)
{
this
.
setState
(
{
loading
:
true
}
)
fetch
(
'https://restcountries.eu/rest/v1/all'
)
.
then
(
response
=>
response
.
json
(
)
)
.
then
(
json
=>
json
.
map
(
country
=>
country
.
name
)
)
.
then
(
countryNames
=>
this
.
setState
(
{
countryNames
,
loading
:
false
}
)
)
}
render
(
)
{
const
{
countryNames
,
loading
}
=
this
.
state
return
(
loading
)
?
<
div
>
Loading
Country
Names
...
<
/
d
i
v
>
:
(
!
countryNames
.
length
)
?
<
div
>
No
country
Names
<
/
d
i
v
>
:
<
ul
>
{
countryNames
.
map
(
(
x
,
i
)
=>
<
li
key
=
{
i
}
>
{
x
}
<
/
l
i
>
)
}
<
/
u
l
>
}
}
render
(
<
CountryList
/
>
,
document
.
getElementById
(
'react-container'
)
)
When the component mounts, just before the fetch
call, we set the loading state to true
. This tells our component, and ultimately our users, that we are in the process of retrieving the data. When we get a response from our fetch
call, we obtain the JSON object and map it to an array of country names. Finally, the country names are added to the state and the DOM updated.
Data Driven Documents (D3) is a JavaScript framework that can be used to construct data visualizations for the browser. D3 provides a rich set of tools that allow us to scale and interpolate data. Additionally, D3 is functional. You compose D3 applications by chaining function calls together to produce a DOM visualization from an array of data.
A timeline is an example of a data visualization. A timeline takes event dates as data and represents that information visually with graphics. Historic events that occurred earlier are represented to the left of those events that occurred later. The space between each event on a timeline in pixels represents the time that has elapsed between the events (Figure 7-6).
This timeline visualizes almost 100 years’ worth of events in just 500 pixels. The process of converting year values to their corresponding pixel values is called interpolation. D3 provides all of the tools necessary for interpolating data ranges from one measurement to another.
Let’s take a look at how to incorporate D3 with React to build this timeline. First, we’ll need to install D3:
npm install [email protected] --save
D3 takes data, typically arrays of objects, and develops visualizations based upon that data. Take a look at the array of historic ski dates. This is the data for our timeline:
const
historicDatesForSkiing
=
[
{
year
:
1879
,
event
:
"Ski Manufacturing Begins"
},
{
year
:
1882
,
event
:
"US Ski Club Founded"
},
{
year
:
1924
,
event
:
"First Winter Olympics Held"
},
{
year
:
1926
,
event
:
"First US Ski Shop Opens"
},
{
year
:
1932
,
event
:
"North America's First Rope Tow Spins"
},
{
year
:
1936
,
event
:
"First Chairlift Spins"
},
{
year
:
1949
,
event
:
"Squaw Valley, Mad River Glen Open"
},
{
year
:
1958
,
event
:
"First Gondola Spins"
},
{
year
:
1964
,
event
:
"Plastic Buckle Boots Available"
}
]
The easiest way to incorporate D3 into a React component is to let React render the UI, then have D3 create and add the visualization. In the following example, D3 is incorporated into a React component. Once the component renders, D3 builds the visualization and adds it to the DOM:
import
d3
from
'd3'
import
{
Component
}
from
'react'
import
{
render
}
from
'react-dom'
class
Timeline
extends
Component
{
constructor
(
{
data
=
[
]
}
)
{
const
times
=
d3
.
extent
(
data
.
map
(
d
=>
d
.
year
)
)
const
range
=
[
50
,
450
]
super
(
{
data
}
)
this
.
state
=
{
data
,
times
,
range
}
}
componentDidMount
(
)
{
let
group
const
{
data
,
times
,
range
}
=
this
.
state
const
{
target
}
=
this
.
refs
const
scale
=
d3
.
time
.
scale
(
)
.
domain
(
times
)
.
range
(
range
)
d3
.
select
(
target
)
.
append
(
'svg'
)
.
attr
(
'height'
,
200
)
.
attr
(
'width'
,
500
)
group
=
d3
.
select
(
target
.
children
[
0
]
)
.
selectAll
(
'g'
)
.
data
(
data
)
.
enter
(
)
.
append
(
'g'
)
.
attr
(
'transform'
,
(
d
,
i
)
=>
'translate('
+
scale
(
d
.
year
)
+
', 0)'
)
group
.
append
(
'circle'
)
.
attr
(
'cy'
,
160
)
.
attr
(
'r'
,
5
)
.
style
(
'fill'
,
'blue'
)
group
.
append
(
'text'
)
.
text
(
d
=>
d
.
year
+
" - "
+
d
.
event
)
.
style
(
'font-size'
,
10
)
.
attr
(
'y'
,
115
)
.
attr
(
'x'
,
-
95
)
.
attr
(
'transform'
,
'rotate(-45)'
)
}
render
(
)
{
return
(
<
div
className
=
"timeline"
>
<
h1
>
{
this
.
props
.
name
}
Timeline
<
/
h
1
>
<
div
ref
=
"target"
>
<
/
d
i
v
>
<
/
d
i
v
>
)
}
}
render
(
<
Timeline
name
=
"History of Skiing"
data
=
{
historicDatesForSkiing
}
/
>
,
document
.
getElementById
(
'react-container'
)
)
In this example, some of the D3 setup occurs in the constructor, but most of the heavy lifting is done by D3 in the componentDidMount
function. Once the DOM is rendered, D3 builds the visualization using Scalable Vector Graphics (SVG). This approach will work and is a good way to quickly incorporate existing D3 visualizations into React components.
We can, however, take this integration one step further by letting React manage the DOM and D3 do the math. Take a look at these three lines of code:
const
times
=
d3
.
extent
(
data
.
map
(
d
=>
d
.
year
))
const
range
=
[
50
,
450
]
const
scale
=
d3
.
time
.
scale
().
domain
(
times
).
range
(
range
)
Both times
and range
are set up in the constructor and added to the component state. times
represents our domain. It contains the values for the earliest year and the latest year. It is calculated by using D3’s extent
function to find the minimum and maximum values in an array of numeric values. range
represents the range in pixels for the timeline. The first date, 1879, will be placed at 0 px on the x-scale and the last date, 1964, will be placed at 450 px on the x-scale.
The next line creates the scale, which is a function that can be used to interpolate the pixel value for any year on our time scale. The scale is created by sending the domain and the range to the D3 time.scale
function. The scale
function is used in the visualization to get the x position for every date that falls between 1879 and 1964.
Instead of creating the scale in componentDidMount
, we can add it to the component in the constructor after we have the domain and range. Now the scale can be accessed anywhere in the component using this.scale(year)
:
constructor
({
data
=
[]})
{
const
times
=
d3
.
extent
(
data
.
map
(
d
=>
d
.
year
))
const
range
=
[
50
,
450
]
super
({
data
})
this
.
scale
=
d3
.
time
.
scale
().
domain
(
times
).
range
(
range
)
this
.
state
=
{
data
,
times
,
range
}
}
Within componentDidMount
, D3 first creates an SVG
element and adds it to the target ref:
d3
.
select
(
target
)
.
append
(
'svg'
)
.
attr
(
'height'
,
200
)
.
attr
(
'width'
,
500
)
Constructing a UI is a task for React. Instead of using D3 for this task, let’s create a Canvas
component that returns an SVG element:
const Canvas = ({children}) => <svg height="200" width="500"> {children} </svg>
Next, D3 selects the svg
element, the first child under the target, and adds a group
element for every data point in our timeline array. After it is added, the group
element is positioned by transforming the x-axis value using the scale
function:
group
=
d3
.
select
(
target
.
children
[
0
])
.
selectAll
(
'g'
)
.
data
(
data
)
.
enter
()
.
append
(
'g'
)
.
attr
(
'transform'
,
(
d
,
i
)
=>
'translate('
+
scale
(
d
.
year
)
+
', 0)'
)
The group
element is a DOM element, so we can let React handle this task too. Here is a TimelineDot
component that can be used to set up group
elements and position them along the x-axis:
const
TimelineDot
=
({
position
})
=>
<
g
transform
=
{
`translate(
${
position
}
,0)`
}
><
/g>
Next, D3 adds a circle
element and some “style” to the group. The text
element gets its value by concatenating the event year with the event title. It then positions and rotates that text around the blue circle:
group
.
append
(
'circle'
)
.
attr
(
'cy'
,
160
)
.
attr
(
'r'
,
5
)
.
style
(
'fill'
,
'blue'
)
group
.
append
(
'text'
)
.
text
(
d
=>
d
.
year
+
" - "
+
d
.
event
)
.
style
(
'font-size'
,
10
)
.
attr
(
'y'
,
115
)
.
attr
(
'x'
,
-
95
)
.
attr
(
'transform'
,
'rotate(-45)'
)
All we need to do is modify our TimelineDot
component to include a circle
element and a text
element that retrieves the text from the properties:
const
TimelineDot
=
(
{
position
,
txt
}
)
=>
<
g
transform
=
{
`
translate(
${
position
}
,0)
`
}
>
<
circle
cy
=
{
160
}
r
=
{
5
}
style
=
{
{
fill
:
'blue'
}
}
/
>
<
text
y
=
{
115
}
x
=
{
-
95
}
transform
=
"rotate(-45)"
style
=
{
{
fontSize
:
'10px'
}
}
>
{
txt
}
<
/
t
e
x
t
>
<
/
g
>
React is now responsible for managing the UI using the virtual DOM. The role of D3 has been reduced, but it still provides some essential functionality that React does not. It helps create the domain and range and constructs a scale
function that we can use to interpolate pixel values from years. This is what our refactored Timeline
component might look like:
class
Timeline
extends
Component
{
constructor
({
data
=
[]})
{
const
times
=
d3
.
extent
(
data
.
map
(
d
=>
d
.
year
))
const
range
=
[
50
,
450
]
super
({
data
})
this
.
scale
=
d3
.
time
.
scale
().
domain
(
times
).
range
(
range
)
this
.
state
=
{
data
,
times
,
range
}
}
render
()
{
const
{
data
}
=
this
.
state
const
{
scale
}
=
this
return
(
<
div
className
=
"timeline"
>
<
h1
>
{
this
.
props
.
name
}
Timeline
<
/h1>
<
Canvas
>
{
data
.
map
((
d
,
i
)
=>
<
TimelineDot
position
=
{
scale
(
d
.
year
)}
txt
=
{
`
${
d
.
year
}
-
${
d
.
event
}
`
}
/>
)}
<
/Canvas>
<
/div>
)
}
}
We can integrate just about any JavaScript library with React. The lifecycle functions are the place where other JavaScript can pick up where React leaves off. However, we should avoid adding libraries that manage the UI: that’s React’s job.
A higher-order component, or HOC, is a simply a function that takes a React component as an argument and returns another React component. Typically, HOCs wrap the incoming component with a class that maintains state or has functionality. Higher-order components are the best way to reuse functionality across React components.
An HOC allows us to wrap a component with another component. The parent component can hold state or contain functionality that can be passed down to the composed component as properties. The composed component does not need to know anything about the implementation of an HOC other than the names of the properties and methods that it makes available.
Take a look at this PeopleList
component. It loads random users from an API and renders a list of member names. While the users are loading, a loading message is displayed. Once they have loaded, they are displayed on the DOM:
import
{
Component
}
from
'react'
import
{
render
}
from
'react-dom'
import
fetch
from
'isomorphic-fetch'
class
PeopleList
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
data
:
[],
loaded
:
false
,
loading
:
false
}
}
componentWillMount
()
{
this
.
setState
({
loading
:
true
})
fetch
(
'https://randomuser.me/api/?results=10'
)
.
then
(
response
=>
response
.
json
())
.
then
(
obj
=>
obj
.
results
)
.
then
(
data
=>
this
.
setState
({
loaded
:
true
,
loading
:
false
,
data
}))
}
render
()
{
const
{
data
,
loading
,
loaded
}
=
this
.
state
return
(
loading
)
?
<
div
>
Loading
...
<
/div> :
<
ol
className
=
"people-list"
>
{
data
.
map
((
person
,
i
)
=>
{
const
{
first
,
last
}
=
person
.
name
return
<
li
key
=
{
i
}
>
{
first
}
{
last
}
<
/li>
})}
<
/ol>
}
}
render
(
<
PeopleList
/>
,
document
.
getElementById
(
'react-container'
)
)
PeopleList
incorporates a getJSON
call from jQuery to load people from a JSON API. When the component is rendered, it displays a loading message or renders a list of names based upon whether or not the loading state is true
.
If we harness this loading functionality, we can reuse it across components. We could create a higher-order component, the DataComponent
, that can be used to create React components that load data. To use the DataComponent
, we strip the PeopleList
of state and create a stateless functional component that receives data via props:
import
{
render
}
from
'react-dom'
const
PeopleList
=
(
{
data
}
)
=>
<
ol
className
=
"people-list"
>
{
data
.
results
.
map
(
(
person
,
i
)
=>
{
const
{
first
,
last
}
=
person
.
name
return
<
li
key
=
{
i
}
>
{
first
}
{
last
}
<
/
l
i
>
}
)
}
<
/
o
l
>
const
RandomMeUsers
=
DataComponent
(
PeopleList
,
"https://randomuser.me/api/"
)
render
(
<
RandomMeUsers
count
=
{
10
}
/
>
,
document
.
getElementById
(
'react-container'
)
)
Now we are able to create a RandomMeUsers
component that always loads and displays users from the same source, randomuser.me. All we have to do is provide the count of how many users we wish to load. The data handling has been moved into the HOC, and the UI is handled by the PeopleList
component. The HOC provides the state for loading and the mechanism to load data and change its own state. While data is loading, the HOC displays a loading message. Once the data has loaded, the HOC handles mounting the PeopleList
and passing it people as the data
property:
const
DataComponent
=
(
ComposedComponent
,
url
)
=>
class
DataComponent
extends
Component
{
constructor
(
props
)
{
super
(
props
)
this
.
state
=
{
data
:
[],
loading
:
false
,
loaded
:
false
}
}
componentWillMount
()
{
this
.
setState
({
loading
:
true
})
fetch
(
url
)
.
then
(
response
=>
response
.
json
())
.
then
(
data
=>
this
.
setState
({
loaded
:
true
,
loading
:
false
,
data
}))
}
render
()
{
return
(
<
div
className
=
"data-component"
>
{(
this
.
state
.
loading
)
?
<
div
>
Loading
...
<
/div> :
<
ComposedComponent
{...
this
.
state
}
/>
}
<
/div>
)
}
}
Notice that DataComponent
is actually a function. All higher-order components are functions. ComposedComponent
is the component that we will wrap. The returned class, DataComponent
, stores and manages the state. When that state changes and the data has loaded, the ComposedComponent
is rendered and that data is passed to it as a property.
This HOC can be used to create any type of data component. Let’s take a look at how DataComponent
can be reused to create a CountryDropDown
that is populated with a country name for every country in the world delivered from the restcountries.eu API:
import
{
render
}
from
'react-dom'
const
CountryNames
=
(
{
data
,
selected
=
""
}
)
=>
<
select
className
=
"people-list"
defaultValue
=
{
selected
}
>
{
data
.
map
(
(
{
name
}
,
i
)
=>
<
option
key
=
{
i
}
value
=
{
name
}
>
{
name
}
<
/
o
p
t
i
o
n
>
)
}
<
/
s
e
l
e
c
t
>
const
CountryDropDown
=
DataComponent
(
CountryNames
,
"https://restcountries.eu/rest/v1/all"
)
render
(
<
CountryDropDown
selected
=
"United States"
/
>
,
document
.
getElementById
(
'react-container'
)
)
The CountryNames
component obtains the country names via props. DataComponent
handles loading and passing information about each country.
Notice that the CountryNames
component also has a selected
property. This property should cause the component to select “United States” by default. However, at present, it is not working. We did not pass the properties to the composed component from our HOC.
Let’s modify our HOC to pass any properties that it receives down to the composed component:
render
(
)
{
return
(
<
div
className
=
"data-component"
>
{
(
this
.
state
.
loading
)
?
<
div
>
Loading
...
<
/
d
i
v
>
:
<
ComposedComponent
{
...
this
.
state
}
{
...
this
.
props
}
/
>
}
<
/
d
i
v
>
)
}
Now the HOC passes state and props down to the composed component. If we run this code now, we will see that the CountryDropDown
preselects “United States”.
Let’s take a look at another HOC. We developed a HiddenMessage
component earlier in this chapter. The ability to show or hide content is something that can be reused. In this next example, we have an Expandable
HOC that functions similarly to the HiddenMessage
component. You can show or hide content based upon the Boolean property collapsed
. This HOC also provides a mechanism for toggling the collapsed
property (Example 7-3).
import
{
Component
}
from
'react'
const
Expandable
=
ComposedComponent
=>
class
Expandable
extends
Component
{
constructor
(
props
)
{
super
(
props
)
const
collapsed
=
(
props
.
hidden
&&
props
.
hidden
===
true
)
?
true
:
false
this
.
state
=
{
collapsed
}
this
.
expandCollapse
=
this
.
expandCollapse
.
bind
(
this
)
}
expandCollapse
()
{
let
collapsed
=
!
this
.
state
.
collapsed
this
.
setState
({
collapsed
})
}
render
()
{
return
<
ComposedComponent
expandCollapse
=
{
this
.
expandCollapse
}
{...
this
.
state
}
{...
this
.
props
}
/>
}
}
The Expandable
HOC takes a ComposedComponent
and wraps it with state and functionality that allows it to show or hide content. Initially, the collapsed state is set using incoming properties, or it defaults to false
. The collapsed state is passed down to the ComposedComponent
as a property.
This component also has a method for toggling the collapsed state called expandCollapse
. This method is also passed down to the ComposedComponent
. Once invoked, it will change the collapsed state and update the ComposedComponent
with the new state.
If the properties of the DataComponent
are changed by a parent, the component will update the collapsed state and pass the new state down to the ComposedComponent
as a property.
Finally, all state and props are passed down to the ComposedComponent
. Now we can use this HOC to create several new components. First, let’s use it to create the HiddenMessage
component that we defined earlier in this chapter:
const
ShowHideMessage
=
({
children
,
collapsed
,
expandCollapse
})
=>
<
p
onClick
=
{
expandCollapse
}
>
{(
collapsed
)
?
children
.
replace
(
/[a-zA-Z0-9]/g
,
"x"
)
:
children
}
<
/p>
const
HiddenMessage
=
Expandable
(
ShowHideMessage
)
Here we create a HiddenMessage
component that will replace every letter or number in a string with an “x” when the collapsed
property is true
. When the collapsed
property is false
, the message will be shown. Try this HiddenMessage
component out in the HiddenMessage
s component that we defined earlier in this chapter.
Let’s use this same HOC to create a button that shows and hides hidden content in a div
. In the following example, the MenuButton
can be used to create PopUpButton
, a component that toggles content display:
class
MenuButton
extends
Component
{
componentWillReceiveProps
(
nextProps
)
{
const
collapsed
=
(
nextProps
.
collapsed
&&
nextProps
.
collapsed
===
true
)
?
true
:
false
this
.
setState
({
collapsed
})
}
render
()
{
const
{
children
,
collapsed
,
txt
,
expandCollapse
}
=
this
.
props
return
(
<
div
className
=
"pop-button"
>
<
button
onClick
=
{
expandCollapse
}
>
{
txt
}
<
/button>
{(
!
collapsed
)
?
<
div
className
=
"pop-up"
>
{
children
}
<
/div> :
""
}
<
/div>
)
}
}
const
PopUpButton
=
Expandable
(
MenuButton
)
render
(
<
PopUpButton
hidden
=
{
true
}
txt
=
"toggle popup"
>
<
h1
>
Hidden
Content
<
/h1>
<
p
>
This
content
will
start
off
hidden
<
/p>
<
/PopUpButton>,
document
.
getElementById
(
'react-container'
)
)
The PopUpButton
is created with the MenuButton
component. It will pass the collapsed state along with the function to change that state to the MenuButton
as properties. When users click on the button, it will invoke expandCollapse
and toggle the collapsed state. When the state is collapsed, we only see a button. When it is expanded we see a button and a div
with the hidden content.
Higher-order components are a great way to reuse functionality and abstract away the details of how component state or lifecycle are managed. They will allow you to produce more stateless functional components that are solely responsible for the UI.
State management in React is great. We could build a lot of applications using React’s built-in state management system. However, when our applications get larger, state becomes a little bit harder for us to wrap our heads around. Keeping state in one place at the root of your component tree will help make this task easier, but even then, your application may grow to a point where it makes the most sense to isolate state data in its own layer, independent of the UI.
One of the benefits of managing state outside of React is that it will reduce the need for many, if any, class components. If you are not using state, it is easier to keep most of your components stateless. You should only need to create a class when you need lifecycle functions, and even then you can isolate class functionality to HOCs and keep components that only contain UI stateless. Stateless functional components are easier to understand and easier to test. They are pure functions, so they fit into strictly functional applications quite nicely.
Managing state outside of React could mean a lot of different things. You can use React with Backbone Models, or with any other MVC library that models state. You can create your own system for managing state. You can manage state using global variables or localStorage
and plain JavaScript. Managing state outside of React simply means not using React state or setState
in your applications.
Back in Chapter 3, we created a ticking clock that followed the rules of functional programming. The entire application consists of functions and higher-order functions that are composed into larger functions that compose a startTicking
function that starts the clock and displays the time in the console:
const
startTicking
=
()
=>
setInterval
(
compose
(
clear
,
getCurrentTime
,
abstractClockTime
,
convertToCivilianTime
,
doubleDigits
,
formatClock
(
"hh:mm:ss tt"
),
display
(
log
)
),
oneSecond
()
)
startTicking
()
But instead of displaying the clock in the console, what if we displayed it in the browser? We could build a React component to display the clock time in a div
:
const
AlarmClockDisplay
=
({
hours
,
minutes
,
seconds
,
ampm
})
=>
<
div
className
=
"clock"
>
<
span
>
{
hours
}
<
/span>
<
span
>:<
/span>
<
span
>
{
minutes
}
<
/span>
<
span
>:<
/span>
<
span
>
{
seconds
}
<
/span>
<
span
>
{
ampm
}
<
/span>
<
/div>
This component takes in properties for hours, minutes, seconds, and time of day. It then creates a DOM where those properties can be displayed.
We could replace the log method with a render
method and send our component to be used to render the civilian time, with leading zeros added to values less than 10:
const
startTicking
=
(
)
=>
setInterval
(
compose
(
getCurrentTime
,
abstractClockTime
,
convertToCivilianTime
,
doubleDigits
,
render
(
AlarmClockDisplay
)
)
,
oneSecond
(
)
)
startTicking
(
)
The render
method will need to be a higher-order function. It will need to take the AlarmClockDisplay
as a property initially when the startTicking
method is composed and hang on to it. Eventually, it will need to use that component to render the display with the formatted time every second:
const
render
=
Component
=>
civilianTime
=>
ReactDOM
.
render
(
<
Component
{...
civilianTime
}
/>
,
document
.
getElementById
(
'react-container'
)
)
The higher-order function for render
invokes ReactDOM.render
every second and updates the DOM. This approach takes advantage of React’s speedy DOM rendering, but does not require a component class with state.
The state of this application is managed outside of React. React allowed us to keep our functional architecture in place by providing our own higher-order function that renders a component with ReactDOM.render
. Managing state outside of React is not a requirement, it is simply another option. React is a library, and it is up to you to decide how best to use it in your applications.
Next, we will introduce Flux, a design pattern that was created as an alternative to state management in React.
Flux is a design pattern developed at Facebook that was designed to keep data flowing in one direction. Before Flux was introduced, web development architecture was dominated by variations of the MVC design pattern. Flux is an alternative to MVC, an entirely different design pattern that complements the functional approach.
What does React or Flux have to do with functional JavaScript? For starters, a stateless functional component is a pure function. It takes in instructions as props and returns UI elements. A React class uses state or props as input and also will produce UI elements. React components are composed into a single component. Immutable data provides the component with input and output as UI elements are returned:
const
Countdown
=
({
count
})
=>
<
h1
>
{
count
}
<
/h1>
Flux provides us with a way to architect web applications that complements how React works. Specifically, Flux provides a way to provide the data that React will use to create the UI.
In Flux, application state data is managed outside of React components in stores. Stores hold and change the data, and are the only thing that can update a view in Flux. If a user were to interact with a web page—say, click a button or submit a form—then an action would be created to represent the user’s request. An action provides the instructions and data required to make a change. Actions are dispatched using a central control component called the dispatcher. The dispatcher is designed to queue up our actions and dispatch them to the appropriate store. Once a store receives an action, it will use it as instructions to modify state and update the view. Data flows in one direction: action to a dispatcher to the store and finally to the view (Figure 7-7).
Actions and state data are immutable in Flux. Actions can be dispatched from a view, or they can come from other sources, typically a web server.
Every change requires an action. Every action provides the instructions to make the change. Actions also serve as receipts that tell us what has changed, what data was used to make the change, and where the action originated. This pattern causes no side effects. The only thing that can make a change is a store. Stores update the data, views render those updates in the UI, and actions tell us how and why the changes have occurred.
Restricting the data flow of your application to this design pattern will make your application much easier to fix and scale. Take a look at the application in Figure 7-8. We can see that every dispatched action has been logged to the console. These actions tell us about how we got to the current UI that is displaying a giant number 3.
As we can see, the last state change that occurred was a TICK. It changed the count to 3 from the count before that, which looks to have been a 4. The actions tell us how the state has been changing. We can trace the actions back to the source to see that first change was to a 9, so presumably this app is counting down from 10.
Let’s take a look at how this countdown is constructed using the Flux design pattern. We will introduce each part of the design pattern and discuss its contribution to the unidirectional data flow that makes up this countdown.
Let’s begin by looking at the view, a React stateless component. Flux will manage our application state, so unless you need a lifecycle function you will not need class components.
The countdown view takes in the count to display as a property. It also receives a couple of functions, tick
and reset
:
const
Countdown
=
({
count
,
tick
,
reset
})
=>
{
if
(
count
)
{
setTimeout
(()
=>
tick
(),
1000
)
}
return
(
count
)
?
<
h1
>
{
count
}
<
/h1> :
<
div
onClick
=
{()
=>
reset
(
10
)}
>
<
span
>
CELEBRATE
!!!<
/span>
<
span
>
(
click
to
start
over
)
<
/span>
<
/div>
}
When this view renders it will display the count, unless the count is 0, in which case it will display a message instructing the user to “CELEBRATE!!!” If the count is not 0, then the view sets a timeout, waits for a second, and invokes a TICK.
When the count is 0, this view will not invoke any other action creators until a user clicks the main div
and triggers a reset. This resets the count to 10 and starts the whole countdown process over again.
Using Flux does not mean that you cannot have state in any of your view components. It means that application state is not managed in your view components. For example, Flux can manage the dates and times that make up timelines. It would not be off-limits to use a timeline component that has internal state to visualize your application’s timelines.
State should be used sparingly—only when needed, from reusable components that internally manage their own state. The rest of the application does not need to be “aware” of a child component’s state.
Actions provide the instructions and data that the store will use to modify the state. Action creators are functions that can be used to abstract away the nitty-gritty details required to build an action. Actions themselves are objects that at minimum contain a type
field. The action type is typically an uppercase string that describes the action. Additionally, actions may package any data required by the store. For example:
const
countdownActions
=
dispatcher
=>
(
{
tick
(
currentCount
)
{
dispatcher
.
handleAction
(
{
type
:
'TICK'
,
count
:
count
-
1
}
)
}
,
reset
(
count
)
{
dispatcher
.
handleAction
(
{
type
:
'RESET'
,
count
}
)
}
}
)
When countdown action creators are loaded, the dispatcher is sent as an argument. Every time a TICK or a RESET is invoked, the dispatcher’s handleAction
method is invoked, which “dispatches” the action object.
There is only ever one dispatcher, and it represents the air traffic control part of this design pattern. The dispatcher takes the action, packages it with some information about where the action was generated, and sends it on to the appropriate store or stores that will handle the action.
Although Flux is not a framework, Facebook does open source a Dispatcher
class that you can use. How dispatchers are implemented is typically standard, so it is better to use Facebook’s dispatcher rather than coding your own:
import
Dispatcher
from
'flux'
class
CountdownDispatcher
extends
Dispatcher
{
handleAction
(
action
)
{
console
.
log
(
'dispatching action:'
,
action
)
this
.
dispatch
({
source
:
'VIEW_ACTION'
,
action
})
}
}
When handleAction
is invoked with an action, it is dispatched along with some data about where the action originated. When a store is created, it is registered with the dispatcher and starts listening for actions. When an action is dispatched it is handled in the order that it was received and sent to the appropriate stores.
Stores are objects that hold the application’s logic and state data. Stores are similar to models in the MVC pattern, but stores are not restricted to managing data in a single object. It is possible to build Flux applications that consist of a single store that manages many different data types.
Current state data can be obtained from a store via properties. Everything a store needs to change state data is provided in the action. A store will handle actions by type and change their data accordingly. Once data is changed, the store will emit an event and notify any views that have subscribed to the store that their data has changed. Let’s take a look at an example:
import
{
EventEmitter
}
from
'events'
class
CountdownStore
extends
EventEmitter
{
constructor
(
count
=
5
,
dispatcher
)
{
super
()
this
.
_count
=
count
this
.
dispatcherIndex
=
dispatcher
.
register
(
this
.
dispatch
.
bind
(
this
)
)
}
get
count
()
{
return
this
.
_count
}
dispatch
(
payload
)
{
const
{
type
,
count
}
=
payload
.
action
switch
(
type
)
{
case
"TICK"
:
this
.
_count
=
this
.
_count
-
1
this
.
emit
(
"TICK"
,
this
.
_count
)
return
true
case
"RESET"
:
this
.
_count
=
count
this
.
emit
(
"RESET"
,
this
.
_count
)
return
true
}
}
}
This store holds the countdown application’s state, the count. The count can be accessed through a read-only property. When actions are dispatched, the store uses them to change the count. A TICK action decrements the count. A RESET action resets the count entirely with data that is included with the action.
Once the state has changed, the store emits an event to any views that may be listening.
Now that you understand how data flows through each part of a Flux application, let’s take a look at how all these parts get connected:
const
appDispatcher
=
new
CountdownDispatcher
()
const
actions
=
countdownActions
(
appDispatcher
)
const
store
=
new
CountdownStore
(
10
,
appDispatcher
)
const
render
=
count
=>
ReactDOM
.
render
(
<
Countdown
count
=
{
count
}
{...
actions
}
/>
,
document
.
getElementById
(
'react-container'
)
)
store
.
on
(
"TICK"
,
()
=>
render
(
store
.
count
))
store
.
on
(
"RESET"
,
()
=>
render
(
store
.
count
))
render
(
store
.
count
)
First, we create the appDispatcher
. Next, we use the appDispatcher
to generate our action creators. Finally, the appDispatcher
is registered with our store, and the store sets the initial count to 10.
The render
method is used to render the view with a count that it receives as an argument. It also passes the action creators to the view as properties.
Finally, some listeners are added to the store, which completes the circle. When the store emits a TICK
or a RESET
, it yields a new count, which is immediately rendered in the view. After that, the initial view is rendered with the store’s count. Every time the view emits a TICK
or RESET
, the action is sent through this circle and eventually comes back to the view as data that is ready to be rendered.
There are different approaches to the implementation of Flux. A few libraries have been open-sourced based upon specific implementations of this design pattern. Here are a few approaches to Flux worth mentioning:
Facebook’s Flux is the design pattern that we just covered. The Flux library includes an implementation of a dispatcher.
A simplified approach to unidirectional data flow that focuses on actions, stores, and views.
A Flux implementation that allows you to build Flux modules through extending JavaScript classes.
A Flux framework created by Yahoo for working with isomorphic Flux applications. Isomorphic applications will be discussed in Chapter 12.
A Flux-like library that achieves modularity through functions instead of objects.
A state management library that uses observables to respond to changes in state.
All of these implementations have stores, actions, and a dispatch mechanism, and favor React components as the view layer. They are all variations of the Flux design pattern, which at its core is all about unidirectional data flow.
Redux has quickly become one of the more popular Flux frameworks. The next chapter covers how to use Redux to construct functional data architectures for your client applications.