React Setup

Part 2, Lesson 5



Let’s turn our attention to the client-side and add React


React is a declarative, component-based, JavaScript library for building user interfaces.

If you’re new to React, review the official tutorial and the excellent Why did we build React? blog post. You may also want to step through the Intro to React tutorial to learn more about Babel and Webpack - and how they work beneath the scenes.

Make sure you have Node and NPM installed before continuing:

$ node -v
v10.4.1

$ npm -v
6.1.0

Project Setup

We’ll use the amazing Create React App CLI to generate a boilerplate that’s all set up and ready to go.

Again, it’s important to understand what’s happening under the hood with Webpack and Babel. For more, check out the Intro to React tutorial.

Start by installing Create React App globally:

$ npm install create-react-app@1.5.2 --global

Add a new directory to “services” called “client”, and then cd into the newly created directory and create the boilerplate:

$ create-react-app .

Along with creating the basic project structure, this will also install all dependencies. Once done, start the server:

$ npm start

After staring the server, Create React App automatically launches the app in your default browser on http://localhost:3000.

Ensure all is well, and then kill the server.

Next, to simplify the development process, remove the package-lock.json file. Let’s also tell npm not to create one, during future module installations, for this project:

$ echo 'package-lock=false' >> .npmrc

Review the npm docs for more info on the .npmrc config file.

Now we’re ready build our first component!

First Component

First, to simplify the structure, remove the App.css, App.js, App.test.js, and index.css from the “src” folder, and then update index.js:

import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return (
    <section className="section">
      <div className="container">
        <div className="columns">
          <div className="column is-one-third">
            <br/>
            <h1 className="title is-1 is-1">All Users</h1>
            <hr/><br/>
          </div>
        </div>
      </div>
    </section>
  )
};

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

What’s happening?

  1. After importing the React and ReactDom classes, we created a functional component called App, which returns JSX.
  2. We then used the render method from ReactDOM to mount the App to the DOM into the HTML element with an ID of root.

    Take note of <div id="root"></div> within the index.html file in the “public” folder.

Add Bulma to index.html (found in the “public” folder) in the head:

<link
  href="//cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css"
  rel="stylesheet"
>

Start the server again to see the changes in the browser:

$ npm start

Class-based Component

Update index.js:

import React, { Component } from 'react';  // new
import ReactDOM from 'react-dom';


// new
class App extends Component {
  constructor() {
    super();
  }
  render() {
    return (
      <section className="section">
        <div className="container">
          <div className="columns">
            <div className="column is-one-third">
              <br/>
              <h1 className="title is-1">All Users</h1>
              <hr/><br/>
            </div>
          </div>
        </div>
      </section>
    )
  }
};

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

What’s happening?

  1. We created a class-based component, which runs automatically when an instance is created (behind the scenes).
  2. When run, super() calls the constructor of Component, which App extends from.

You may have already noticed, but the output in the browser is the exact same as before, despite using a class-based component. We’ll look at the differences between the two shortly!

AJAX

To connect the client to the server, add a getUsers() method to the App class, which uses Axios to manage the AJAX call:

getUsers() {
  axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)
  .then((res) => { console.log(res); })
  .catch((err) => { console.log(err); });
}

Install Axios:

$ npm install axios@0.17.1 --save

Add the import:

import axios from 'axios';

You should now have:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';  // new


class App extends Component {
  constructor() {
    super();
  }
  // new
  getUsers() {
    axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)
    .then((res) => { console.log(res); })
    .catch((err) => { console.log(err); });
  }
  render() {
    return (
      <section className="section">
        <div className="container">
          <div className="columns">
            <div className="column is-one-third">
              <br/>
              <h1 className="title is-1">All Users</h1>
              <hr/><br/>
            </div>
          </div>
        </div>
      </section>
    )
  }
};

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

To connect this up to the users service, open a new terminal window, navigate to the project root, and update the containers:

$ docker-compose -f docker-compose-dev.yml up -d

Ensure the app is working in the browser, and then run the tests:

$ docker-compose -f docker-compose-dev.yml run users python manage.py test

Now, turning back to React, we need to add the environment variable, process.env.REACT_APP_USERS_SERVICE_URL. Kill the Create React App server (if it’s running), and then run:

$ export REACT_APP_USERS_SERVICE_URL=http://localhost

All custom environment variables must begin with REACT_APP_. For more, check out the official docs.

We still need to call the getUsers() method, which we can do, for now, in the constructor():

constructor() {
  super();
  this.getUsers();  // new
}

Run the server - via npm start - and then within Chrome DevTools, open the JavaScript Console. You should see the following error:

Failed to load http://192.168.99.100/users:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.

In short, we’re making a cross-origin AJAX request (from http://localhost:3000 to http://localhost), which is a violation of the browser’s “same origin policy”. Fortunately, we can use the Flask-CORS extension to handle this.

Within the “users” directory, add Flask-CORS to the requirements.txt file:

flask-cors==3.0.6

To keep things simple, let’s allow cross origin requests on all routes, from any domain. Simply update create_app() in services/users/project/__init__.py like so:

def create_app(script_info=None):

    # instantiate the app
    app = Flask(__name__)

    # enable CORS
    CORS(app)  # new

    # set config
    app_settings = os.getenv('APP_SETTINGS')
    app.config.from_object(app_settings)

    # set up extensions
    db.init_app(app)
    toolbar.init_app(app)

    # register blueprints
    from project.api.users import users_blueprint
    app.register_blueprint(users_blueprint)

    # shell context for flask cli
    @app.shell_context_processor
    def ctx():
        return {'app': app, 'db': db}

    return app

Add the import at the top:

from flask_cors import CORS

To test, start by updating the containers:

$ docker-compose -f docker-compose-dev.yml up -d --build

Then, update and seed the database:

$ docker-compose -f docker-compose-dev.yml run users python manage.py recreate_db

$ docker-compose -f docker-compose-dev.yml run users python manage.py seed_db

Fire back up both servers, open the JavaScript Console again, and this time you should see the results of console.log(res);:

react ajax request

Let’s parse the JSON object:

getUsers() {
  axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)
  .then((res) => { console.log(res.data.data); })  // new
  .catch((err) => { console.log(err); })
}

Now you should have an array with two objects in the JavaScript Console:

[
  {
    "active": true,
    "email": "hermanmu@gmail.com",
    "id": 1,
    "username": "michael"
  },
  {
    "active": true,
    "email": "michael@mherman.org",
    "id": 2,
    "username": "michaelherman"
  }
]

Before we move on, we need to do a quick refactor. Remember how we called the getUsers() method in the constructor?

constructor() {
  super();
  this.getUsers();
};

Well, the constructor() fires before the component is mounted to the DOM. What would happen if the AJAX request took longer than expected and the component mounted before the request completed? This introduces a race condition. Fortunately, React makes it fairly simple to correct this via Lifecycle Methods.

Component Lifecycle Methods

Class-based components have several functions available to them that execute at certain times during the life of the component. These are called Lifecycle Methods. Take a quick look at the official documentation to learn about each method and when each is called. Also, check out this excellent diagram from Dan Abramov:

react lifecycle

The AJAX call should be made in the componentDidMount() method:

componentDidMount() {
  this.getUsers();
};

Update the component:

class App extends Component {
  // new
  constructor() {
    super();
  };
  // new
  componentDidMount() {
    this.getUsers();
  };
  getUsers() {
    axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)
    .then((res) => { console.log(res.data.data); })
    .catch((err) => { console.log(err); })
  }
  render() {
    return (
      <section className="section">
        <div className="container">
          <div className="columns">
            <div className="column is-one-third">
              <br/>
              <h1 className="title is-1">All Users</h1>
              <hr/><br/>
            </div>
          </div>
        </div>
      </section>
    )
  }
};

Make sure everything still works as it did before.

State

To add the state - i.e., the users - to the component we need to use setState(), which is an asynchronous function used to update state.

Update getUsers():

getUsers() {
  axios.get(`${process.env.REACT_APP_USERS_SERVICE_URL}/users`)  // new
  .then((res) => { this.setState({ users: res.data.data.users }); })
  .catch((err) => { console.log(err); });
};

Add state to the constructor:

constructor() {
  super();
  // new
  this.state = {
    users: []
  };
};

So, this.state adds the state property to the class and sets users to an empty array.

Review Using State Correctly from the official docs.

Finally, update the render() method to display the data returned from the AJAX call to the end user:

render() {
  return (
    <section className="section">
      <div className="container">
        <div className="columns">
          <div className="column is-one-third">
            <br/>
            <h1 className="title is-1">All Users</h1>
            <hr/><br/>
            {/* new */}
            {
              this.state.users.map((user) => {
                return (
                  <h4
                    key={user.id}
                    className="box title is-4"
                  >{ user.username }
                  </h4>
                )
              })
            }
          </div>
        </div>
      </div>
    </section>
  )
}

What’s happening?

  1. We iterated over the users (from the AJAX request) and created a new H4 element. This is why we needed to set an initial state of an empty array - it prevents map from exploding.
  2. key is used by React to keep track of each element. Review the official docs for more.

Functional Component

Let’s create a new component for the users list. Add a new folder called “components” to “src”. Add a new file to that folder called UsersList.jsx:

import React from 'react';

const UsersList = (props) => {
  return (
    <div>
      {
        props.users.map((user) => {
          return (
            <h4
              key={user.id}
              className="box title is-4"
            >{ user.username }
            </h4>
          )
        })
      }
    </div>
  )
};

export default UsersList;

Why did we use a functional component here rather than a class-based component?

Notice how we used props instead of state in this component. Essentially, you can pass state to a component with either props or state:

  1. Props - data flows down via props (from state to props), read only
  2. State - data is tied to a component, read and write

For more, check out ReactJS: Props vs. State.

It’s a good practice to limit the number of class-based (stateful) components since they can manipulate state and are, thus, less predictable. If you just need to render data (like in the above case), then use a functional (state-less) component.

Now we need to pass state from the parent to the child component via props. First, add the import to index.js:

import UsersList from './components/UsersList';

Then, update the render() method:

render() {
  return (
    <section className="section">
      <div className="container">
        <div className="columns">
          <div className="column is-one-third">
            <br/>
            <h1 className="title is-1">All Users</h1>
            <hr/><br/>
            <UsersList users={this.state.users}/>
          </div>
        </div>
      </div>
    </section>
  )
}

Review the code in each component and add comments as necessary. Commit your code.