The minimum you need to know in order to build a React application

Recently I had to build a small React interface with a bit of time pressure. The problem was that I didn’t know anything about React so I had to learn quickly. Normally I like to read the documentation in depth but this time I decided to focus on coding and just look the documentation as I get stuck. As a result, what I learned is “the minimum you need in order to know to build a React application”. Let’s go.

The application consisted in two tabs, each one showing a click counter that is initialized in response to a (faked) asynchronous query. Like this.

The whole thing took 8 hours to build. Hopefully this post can save you that time!

The post is organized like this:

  1. Disclaimer
  2. Creating a React project.
  3. Understanding the concept of component.
  4. Building the tabs user interface, where I deal with some practical aspects of using React.

Disclaimer

This quick tutorial will condense the essential information in order to build React applications, but remember that I just dedicated 8h to it. Most of the things I mention here can be plain wrong and there may be important omissions. If you are planning to build some serious application you need additional sources of information, probably the official documentation, which looks quite complete and easy to process.

Creating a React project

React projects are quite a complex because they put together several different technologies. However, there is a project called create-react-app that configures a lot of things automatically. You just need to install it and run it in order to create your React project:

npm install -g create-react-app
create-react-app my-app

Here is the list of the things it configures.

The following command runs your project:

npm start

The created project contains no configuration files. Probably all is defined by default in some dependency. However, it is possible to eject from this situation and get all the configuration files in your project:

npm run eject

Finally, you just run

npm run build

In order to generate the production ready artifacts in a build folder. The build command outputs useful information, for example that you have to put the path of your application in the homepage attribute in package.json. In my case, the application is installed in the “/react-post” path, so I have to set it to “/react-post”:

{
  "name": "react-post",
  "version": "0.1.0",
  "private": true,
  "homepage": "/react-post",
  "dependencies": {
    ...
  }
  ...
}

React components

Basically React allows you to reuse components for web interfaces. A React component will have some logic and will produce some HTML+CSS code when the time comes.

All starts by a call to ReactDOM.render(<JSXexpression>, <DOMelement>), where the DOMelement is the element where you want to put the result of the JSX expression. If this element is a div with id = “root”, you just call:

document.getElementById("root");

JSX is an extension to Javascript that allows you to write something like HTML inside your Javascript code. Thus, JSX expressions allow you to define the DOM structure that your component will produce.

So in this initial call, the JSXExpression will be a reference to the React component that you want to render, for example:

ReactDOM.render(<MyComponent/>, document.getElementById("root"));

This will make React create an instance of MyComponent and make it “render” some contents that will be added to the root element in the DOM.

To summarize, we are instantiating components by means of JSX expressions and adding them to the DOM.

Let’s see how a component looks like.

Defining your components

Components are subclasses of React.Component.

import React from 'react';

class MyComponent extends React.Component {

}

As such, they inherit a render() method, in charge of returning the contents that will be added to the DOM:

import React from 'react';

class MyComponent extends React.Component {

    render() {
        return <span>hello world</span>;
    }
}

The render method will be also using JSX expressions for this purpose.

(By the way, yes: the syntax coloring in this post gets all messed up with the JSX expressions)

In order to produce some content render, the component needs information. There are two sources of information for a component:

Let’s see each one of them in detail.

State

State can be defined in the constructor, for example:

import React from 'react';
import MUIDataTable from 'mui-datatables';

class MyComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        };
    }

    render() {
        return <span>Hello world clicked {this.state.counter} times</span>;
    }
}

Note how in the previous example the constructor defines a state attribute and the render method has an expression between curly braces referencing it.

As a subclass of React.Component, your components inherit several methods to deal with the state, I know about:

These methods will be called in response to an event, an asynchronous call response, etc. and will trigger a render process. It is important to remember that it is the call to these methods which is forcing a render. If we set directly the state attribute the component will not be rendered.

In the next example, we update the counter in the increaseCounter method:

import React from 'react';
import MUIDataTable from 'mui-datatables';

class MyComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        };
    }

    increaseCounter = ()=>{
        this.setState({
            counter: this.state.counter + 1
        });
    }

    render() {
        return <span onClick={this.increaseCounter}>Hello world clicked {this.state.counter} times</span>;
    }
}

Note how it is using the arrow function syntax. This is in order to have a proper reference in this.

Properties

The other type of information your component may need in order to render are properties which are defined in the references to your component inside JSX expressions. Indeed, in the previous example we are already sending some properties to the span control: we are setting the onClick property and the content of the span, which is the children property.

However, our control is not receiving any property, because the JSX expression including it (in the ReactDOM.render call) is not specifying them. We could parameterize the counter message, like this:

render() {
    return <span onClick={this.increaseCounter}>{this.props.message}{this.state.counter}</span>;
}

Thus we could include our control in the call to ReactDOM.render specifying the message property like this:

ReactDOM.render(<MyComponent message="Hallo Welt Zähler: "/>, document.getElementById('root'));

Here is the code so far. And here is a demo.

BTW, React components can also be a function, but just ignore this and do not very scared if you see one.

Counters inside tabs

Now we want to put the counters inside tabs and show one or the other depending on the selected tab. Selecting one tab would show one counter and selecting the other tab would show us the other counter. Easy, right? well, not that much.

Material-UI

I didn’t do any research on this, so there may be better options. However Material UI it seems a rather popular component repository, which follows the Google Material’s design, whatever that is. Cool components to reuse, in any case.

The MaterialUI Tabs demo page has links to the GitHub repository with the code, which is useful to understand how to use the components.

The tabs

Install Material UI:

npm install @material-ui/core

and add the tabs in the ReactDOM.render() call in src/index.js:

import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

[...]

ReactDOM.render((
    <Tabs value="c1">
        <Tab label="Counter 1" value="c1"/>
        <Tab label="Counter 2" value="c2"/>
    </Tabs>
), document.getElementById('root'));

Basically we nest two Tab components inside a Tabs component. The value property in the Tab instances is used as identifier and the one in the Tabs instance points to the Tab that we want to select. So far we are not placing our component in the tabs.

Here is the code so far and here is a demo.

Selecting the tabs

If you see the resulting application you will notice that the selected tab cannot be changed. This is because the Tabs value property is hardcoded to “c1”. We could set the value property with a variable and add an onChange property to update the variable value. Then we should force the component to re-render. The easiest way to force this render process is to actually make this variable the state of a parent component, so that we can use the setState method to force the rendering of the tree under it.

class TabsContainer extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            selectedTab: "c1"
        }
    }

    changeTab = (event, value)=> {
        this.setState({
            selectedTab:value
        });
    }

    render() {
        return (
            <Tabs value={this.state.selectedTab} onChange={this.changeTab}>
                <Tab label="Counter 1" value="c1"/>
                <Tab label="Counter 2" value="c2"/>
            </Tabs>
        );
    }
}

ReactDOM.render(<TabsContainer/>, document.getElementById('root'));

Now, when changeTab is invoked in response to a click on a tab, the state of TabsContainer will be updated, and that will force the component (and the children tabs) to render again.

The code can be found here and here is a demo.

Showing one span or the other

Now we want to show one counter or the other depending on the selected Tab. It would be nice if this is done as children of the Tab components, but it is not. So basically you set a component under it and show it depending on the selected tab.

You will see the following suggested a lot:

render() {
    return (
        <div>
            <Tabs value={this.state.selectedTab} onChange={this.changeTab}>
                <Tab label="Counter 1" value="c1"/>
                <Tab label="Counter 2" value="c2"/>
            </Tabs>
            {this.state.selectedTab === "c1" && <MyComponent message="First counter: "/>}
            {this.state.selectedTab === "c2" && <MyComponent message="Second counter: "/>}
        </div>
    );
}

Where all the controls are wrapped inside a div (Looks like JSX expressions must evaluate to a single component) and the MyComponent instances are evaluated (between curly braces) conditionally to the value of this.state.selectedTab. This is however a bad idea because a new component gets instantiated on each render. If you see the resulting application, changing tabs makes the counters start at zero every time (because it is a new one).

The code can be found here and here is a demo.

This approach is specially dangerous if the component is listening some event, or if there is a reference to it somewhere in the application, because we will be creating a lot of components that we no longer use whose memory cannot be freed because they are still referenced, a.k.a. memory leak.

Instead we want to instantiate them only once and show them conditionally. We will be using a .hidden class like this:

.hidden {
    display: none;
}

and we will embed the counters in divs, that will be shown depending on the selectedTab state:

render() {
    return (
        <div>
            <Tabs value={this.state.selectedTab} onChange={this.changeTab}>
                <Tab label="Counter 1" value="c1"/>
                <Tab label="Counter 2" value="c2"/>
            </Tabs>
            <div className={this.state.selectedTab !== "c1"?'hidden':''}>
                <MyComponent message="First counter: "/>
            </div>
            <div className={this.state.selectedTab !== "c2"?'hidden':''}>
                <MyComponent message="Second counter: "/>
            </div>
        </div>
    );
}

Thus, react will be intelligent enough to reuse the instances of MyComponent created at each execution of render, which is great from the performance point of view and also not so sensitive to memory leaks as the previous approach.

The code can be found here and here is a demo. Note that now the change of tab does not cause the counter to reinitialize.

Getting some information into the control after an asynchronous call

Finally, sometimes your component needs some information that will come when the component instance is already created and rendered. This is the case, for example, if we want to initialize our counters with some value obtained from a web service. Let’s mimic that with a simple timeout firing 2s after the application starts:

setTimeout(()=>{
    let c1 = 10;
    let c2 = 5;

    // How do we reach our component instances rendered in the dom?

}, 2000);

How do we reach our component instances rendered in the dom?

The only and dangerous approach I have found is to use an event, for example at the document level. Let’s say we use “counter1-init” as event for the first counter and “counter2-init” as event for the second one. Our timeout would look like this:

setTimeout(()=>{
    let c1 = 10;
    let c2 = 5;

    document.dispatchEvent(new CustomEvent("counter1-init", {
        detail: c1
    }));
    document.dispatchEvent(new CustomEvent("counter2-init", {
        detail: c2
    }));
}, 2000);

And our components should listen for these events, for example in the constructor:

class MyComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        };
        this.listener = (event) => {
            this.setState({
                counter: event.detail
            });
        }

        document.addEventListener(this.props.eventName, this.listener);
    }

[...]

Note that we have two events: counter1-init and counter2-init, so the event name to register our component instance is parameterized through the eventName property. In the call to addEventListener we use the eventName property, which has to be set when the component is instantiated in the JSX expression in the TabsContainer.render() method:

[...]
<div className={this.state.selectedTab !== "c1"?'hidden':''}>
    <MyComponent eventName="counter1-init" message="First counter: "/>
</div>
<div className={this.state.selectedTab !== "c2"?'hidden':''}>
    <MyComponent eventName="counter2-init" message="Second counter: "/>
</div>
[...]

The code can be found here and here is a demo.

Components life cycle

I said before that this approach is dangerous because we are keeping a reference to our components (and we rely in React to instantiate and discard them). If React decides to create new instances in each render and discard old ones, as we saw previously, we’ll have a memory leak.

To avoid this risk we have to remove our listener when our component is no longer used. This can be done using the componentDidMount and componentWillUnmount methods inherited from React.Component. These methods are called when the component is inserted to and removed from the tree, respectively. We can add the listener in componentDidMount and remove it in componentWillUmount, like this:

class MyComponent extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        };
    }

    componentDidMount() {
        this.listener = (event) => {
            this.setState({
                counter: event.detail
            });
        }

        document.addEventListener(this.props.eventName, this.listener);
    }

    componentWillUnmount() {
        document.removeEventListener(this.props.eventName, this.listener);
        this.listener = null;
    }

    increaseCounter = ()=>{
        this.setState({
            counter: this.state.counter + 1
        });
    }

    render() {
        return <span onClick={this.increaseCounter}>{this.props.message}{this.state.counter}</span>;
    }

}

The code can be found here and here is a demo.

I did some tests with the previous syntax:

{this.state.selectedTab === "c1" && <MyComponent message="First counter: "/>}

which creates a new MyComponent instance on each call to render(), and there is still a memory leak. Why? I don’t know. In any case the approach for me to follow is not to use that syntax and try to stick with the same instance of my component as much as possible. I’ll do an update if I learn why is it leaking memory.