Chapter 7. Enhancing Components

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.

Component Lifecycles

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.

Mounting 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.

Table 7-1. The component mounting lifecycle
ES6 class React.createClass()
  getDefaultProps()
constructor(props) getInitialState()
componentWillMount() componentWillMount()
render() render()
componentDidMount() componentDidMount()
componentWillUnmount() componentWillUnmount()

Class Constructors

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 = ({ email, picture, name, location }) =>
    <div className="member">
        <img src={picture.thumbnail} alt="" />
        <h1>{name.first} {name.last}</h1>
        <p><a href={"mailto:" + email}>{email}</a></p>
        <p>{location.city}, {location.state}</p>
    </div>

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</span> :
                    (members.length) ? 
                        members.map((user, i) =>
                            <Member key={i} {...user} />
                        ) : 
                        <span>0 members loaded...</span>
                }
                {(error) ? <p>Error Loading Members: error</p> : ""}
      </div>
    )
  }
}

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.

Using setState in componentWillMount

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}</span>
                <span>:</span>
                <span>{minutes}</span>
                <span>:</span>
                <span>{seconds}</span>
                <span>{ampm}</span>
                <button onClick={this.props.onClose}>x</button>
            </div>
        )
    }

}

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.

Updating Lifecycle

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}</h1>
                <div className="color" 
                     style={{ backgroundColor: color }}>
                </div>  
                <StarRating starsSelected={rating} 
                            onRate={onRate} />
            </section>
    }

} 

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).

Figure 7-1. Mounted colors with grey background

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).

Figure 7-2. Rating blue triggers update, and all four components update

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}`)
}
Figure 7-3. One update at a time with shouldComponentUpdate

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.

Figure 7-4. Updating paused with alert

As soon as we clear the alert, the component updates and componentDidUpdate is invoked, clearing the title’s background color (Figure 7-5).

Figure 7-5. componentDidUpdate removes the title highlight

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.

Example 7-1. HiddenMessages component
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}
                    </HiddenMessage>
                )}
            </div>
        )
    }
}

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.

Example 7-2. HiddenMessage component
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

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</h1>
    </Display>,
    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</h1>
        </WhenTruthy>
        <WhenFalsy>
           <h1>Beat it Kid</h1>
        </WhenFalsy>
    </Display>,
    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.

JavaScript Library Integration

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.

React with jQuery

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.

Making Requests with Fetch

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...</div> :
            (!countryNames.length) ?
                <div>No country Names</div> :
                <ul>
                    {countryNames.map(
                        (x,i) => <li key={i}>{x}</li>
                    )}
                </ul>
    }

}

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.

Incorporating a D3 Timeline

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).

Figure 7-6. Timeline data visualization

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</h1>
                <div ref="target"></div>
            </div>
        )
    }

}

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}</text>

    </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.

Higher-Order Components

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}</li>
        })}
    </ol>

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}</option> 
       )}
   </select>  

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...</div> :
                <ComposedComponent {...this.state}
                                   {...this.props} />
            }
        </div>
    )
}

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).

Example 7-3. ./components/hoc/Expandable.js
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 HiddenMessages 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.

Managing State Outside of React

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.

Rendering a Clock

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

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).

Figure 7-7. Facebook’s Flux Design Pattern

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.

Figure 7-8. Countdown app with Flux

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.

Views

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.

State in Components

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 and Action Creators

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.

Dispatcher

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

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.

Putting It All Together

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.

Flux Implementations

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:

Flux

Facebook’s Flux is the design pattern that we just covered. The Flux library includes an implementation of a dispatcher.

Reflux

A simplified approach to unidirectional data flow that focuses on actions, stores, and views.

Flummox

A Flux implementation that allows you to build Flux modules through extending JavaScript classes.

Fluxible

A Flux framework created by Yahoo for working with isomorphic Flux applications. Isomorphic applications will be discussed in Chapter 12.

Redux

A Flux-like library that achieves modularity through functions instead of objects.

MobX

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset