Emacsen's Blog

Modern Web Development with React (Part 4)

In the previous post, we created our first React component. In this post, we'll build on that and explore the issue of state in React, embedding components into components as well as React's component lifecycle.

Immutable Properties

When we left off, we had explored React's concept of props. Props are constants that we can send to a react component at its initialization time and they remain with the component.

Properties do not change. If we want to create a component with a new property, we would create a new component.

Default Properties

Getting back to our Temperature component, we have to manually set our temperature property. If we we to fail to set that property, our web page would break, possibly not showing anything at all, because the props.fahrenheit value would be undefined.

We can set a default property on our component in case a property isn't sent to it.

For that, we'll create a getDefaultProps method, which will return a Javascript object containing default properties. In our example, we'll set a default temperature of 72F.

To do that, create a new method for our Temperature component:

getDefaultProps: function(){
    return {
        "fahrenheit": 72
    }
},

Now we can either set the temperature explicitly as we did before, or we can omit the temperature property and a default value will be set for us.

Getting dynamic with state

So far we've been using a static temperature that we've either been setting manually or using a default value for. Now we're going to create a component that is more dynamic.

Let's imagine that we have an RESTful API /temperature which returns a JSON response in the form of {"fahrenheit": 80}. Instead of setting our component manually, we could use this API endpoint to set our temperature.

While we could use props for this, if we only intend to set the temperature once, we're going to go ahead and use state instead.

React components' state property works similarly to the props property except that values in state are changeable using the setState method, which we'll use. It's important to always use setState to set the values inside state, rather than trying to set them directly.

We'll want to fetch the current temperature as soon as we can. The best time to do this is right before the component will mount to the DOM. So we'll set the React method componentWillMount to retrieve our value and set the temperature. We'll also change our render method to use the state, instead of the prop.

var Temperature = React.createClass({
    componentWillMount: function(){
        request.get('/temperature', function(res){
            this.setState({fahrenheit, res.body.fahrenheit})
        }.bind(this));
    },
    render: function(){
        return (
        <h1>The temperature is {this.state.fahrenheit}F<h1>
        )
});

What's Happening Here

Although we made a small change, there's quite a bit to go over.

Out componentWillMount method will fire just before the component is rendered in the browser, and it will send off a RESTful call to the URL /temperature.

Once that's complete, it will fire off the anonymous function that we've passed request.get, which calls the this.setState method with the newtemperature property.

this returns

You may notice that we've added .bind(this)to our anonymous function call to request.get. That's because we call the this.setState method on it, and this is always set to the current function namespace. We can get around that by binding this, our component, to the anonymous function- thereby giving it access to all the properties of our component, including the setState method.

Look Ma, no render()

The next thing you may notice is that we didn't explicitly call render() after we set our new state. That's because React will do that for us. Once we set a new state, the render method will get re-run and, if necessary, the page will be refreshed. More on this later in the post.

If you've had to work directly with DOM manipulation, then you'll see the clear difference here. We only need to worry about the state of our component through properties and state, and the component will take care of its own display.

There's still a problem

If you run this code, the render() method will be executed while the request.get is still waiting. That's the asynchronous nature of Javascript, and it's generally a good thing.

The problem for us is that in the meantime, our render method will return something like:

The temperature is undefined, or worse, may fail to run at all.

There are two ways we can solve this. The first is that we can set a default temperature, and the second is that we can put some logic in our render method to handle this condition. Let's go through both.

Setting Default State

To start, let's just set a default temperature just like we did in the previous post. For that, we'll use the getInitialState method.

Add the following method to our component:

getInitialState: function(){
    return {
        fahrenheit: 72
    }
},

When the component runs, for a brief period, it will display the temperature as 72 until the request call completes.

Using logic during render

Instead of setting a fake temperature value, we can add some logic to our render method telling the user that we're fetching the temperature.

Let's do that using the original component as displayed early on in the post:

var Temperature = React.createClass({
    componentWillMount: function(){
        request.get('/temperature', function(res){
            this.setState({fahrenheit, res.body.fahrenheit})
        }.bind(this));
    },
    render: function(){
        if (this.state.fahrenheit === undefined){
            return (<h1>Getting temperature...</h1>)
        } else {
            return (
            <h1>The temperature is {this.state.fahrenheit}F<h1>
           )
       }
});

Now while the request is in process, the message Getting temperature will be displayed to the user.

Combining default state with render logic

It's nice to be able to set a message that the temperature isn't yet set, but using undefined is probably not a good idea. Instead, let's set the state of fahrenheit to null. Because null is distinct from undefined, we can test for it explicitly.

So let's do that:

var Temperature = React.createClass({
    getInitialState: function(){
    return {
        fahrenheit: null
        }
    },
    componentWillMount: function(){
        request.get('/temperature', function(res){
            this.setState({fahrenheit, res.body.fahrenheit})
        }.bind(this));
    },
    render: function(){
        if (this.state.fahrenheit === null){
            return (<h1>Getting temperature...</h1>)
        } else {
            return (
            <h1>The temperature is {this.state.fahrenheit}F<h1>
           )
       }
});

Flexibility

We're using the url /temperature for our RESTful call, but that URL could be anything. In fact, we could be setting the state from any function call at all. That's in contrast to some other libraries which dictate exactly how the server needs to lay out its data. Here, we have the ultimate control over that and we can choose an API that makes sense for us.

Multiple components

So far we've only created a single component, but we can create multiple components.

We're going to create one component that displays the temperature in Fahrenheit and one that displays it in Celsius.

var Fahrenheit = React.createClass({
    render: function(){
        return (
        <h1>{this.props.fahrenheit}F</h1>
        )
    }
});

var Celsius = React.createClass({
    render: function(){
        var celsius =  (this.props.fahrenheit - 32) * 5 / 9;
        return (
            <h1>{celsius.toPrecision(1)}C</h1>`It
        )
    }
});

var Temperature = React.createClass({
    componentWillMount: function(){
        request.get('/temperature', function(res){
            this.setState({fahrenheit, res.body.fahrenheit})
        }.bind(this));
    },
    render: function(){
        return (
            <div>
                <Fahrenheit fahrenheit={this.state.fahrenheit} />
                <Celsius fahrenheit={this.state.fahrenheit} />
            </div>
        )
    }
});

Reusable Components

Instead of creating the rendered output directly in the Temperature Component, we now use the Fahrenheit and Celsius components. Using JSX we can treat these components just like regular HTML in our application. This level of abstraction is very useful as we build larger and more complex applications.

In addition to the ease of creation, we do not need to worry about our children object's state. That is handled for us in our Render method. That means that at each component level, we can focus our attention exclusively on that component without worrying about what either the parent component or the child component are doing.

Using Props

You may notice that we're using props for our temperature display, rather than state.

That's because we know that the temperature for each of these components is not going to change, so props make sense. The props for the components does not change, instead, new components are generated.

Making our code more clear

Having the data retrieval in componentWillMount works, but it's not entirely clear initially what's going on. Let's make it more clear by adding a new method to Temperature that explicitly says that we're fetching the temperature:

getTemperature: function(){
    request.get('/temperature', function(res){
        this.setState({fahrenheit, res.body.fahrenheit})
    }.bind(this));
},

Now we'll reference this method in our componentWillMount, so it will become:

componentWillMount: function(){
    this.getTemperature()
},

Custom Methods

Writing our component this way has an added benefit of more readability. It's more obvious what's going on now inside of componentWillMount.

Because React components are normal Javascript objects, we can add new methods to them. The React framework gives us guidelines but generally stays out of the way.

Getting Updates to our Temperature

What if we wanted to get regular updates to the temperature? We can use Javascript builtin setInterval to regularly retrieve the temperature from our API endpoint and update the temperature accordingly. Now that we have getTemperature as its own function, we can do that pretty easily.

We'll change our componentWillMount method to add the setInterval call. setInterval returns an identifier to keep track of the timer- so we'll store that timer in a state variable in case we want to use it later.

componentWillMount: function(){
    var intervalID = window.setInterval(this.getTemperature, 30000);
    this.setState({intervalID: intervalID});
}

So now the component will re-load the temperature from the server every 30 seconds.

The Shadow DOM

You might think that destroying an old component and creating a new one would be slow, but React optimizes this by creating a copy of the DOM in memory and then only editing that DOM. It then create a diff of the two and only modifies on screen what needs to be changed. This is done incredibly quickly, so screen refresh times are very short, in the tens of milliseconds.

Cleaning up after ourselves

In our case, we do not destroy the Temperature component without also closing the window, but it's possible for us to do so in another setting. If we were to do that, the browser would still make the getTemperature call every 30 seconds, because that function is bound to the window object, not just the Temperature instance.

To address this, let's add a new method to our Temperature component that removes the interval call upon component destruction. The method name for this in react is componentWillUnmount:

componentWillUnmount: function(){
    window.clearInterval(this.state.intervalID);
},

Now we're free to create and destroy as many Temperature component instances as we like.

Summing it up so far

In this post we covered using React state, we covered making components that have children components, integrating React components with external data, as well as using the React lifecycle methods to create methods that execute before and after the component mounts.

In the next post, we'll go through how React makes forms easier, as well as touch on using CSS frameworks to help us build our applications more easily.

React