React Setup

Part 1, Chapter 4


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 behind the scenes.

Make sure you have Node and NPM installed before continuing:

$ node -v
v19.7.0

$ npm -v
9.5.0

Project Setup

We'll be using 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.

Add a new directory in "services" called "client", and then cd into the newly created directory and create the boilerplate with npx:

Along with creating the basic project structure, this will also install all dependencies. This will take several minutes. Welcome to modern JavaScript. Once done, start the server:

$ npm start

After starting 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. Now we're ready to build our first component!

First Component

First, to simplify the structure, remove every file and folder from the "services/client/src" folder except index.js and setupTests.js. Then, update index.js:

import { createRoot } from 'react-dom/client';

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">Users</h1>
            <hr/><br/>
          </div>
        </div>
      </div>
    </section>
  )
};

const container = document.getElementById('root');
const root = createRoot(container);

root.render(<App />);

What's happening?

  1. After importing createRoot, we created a functional component called App, which returns JSX.
  2. We then used the render method to mount the App to the DOM to 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.9.3/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 { createRoot } from 'react-dom/client';
import { Component } from 'react';  // new


// 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">Users</h1>
              <hr/><br/>
            </div>
          </div>
        </div>
      </section>
    )
  }
};

const container = document.getElementById('root');
const root = createRoot(container);

root.render(<App />);

What's happening?

  1. We created a class-based component, which runs automatically when an instance is created (behind the scenes).
  2. When the app is 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_API_SERVICE_URL}/users`)
  .then((res) => { console.log(res); })
  .catch((err) => { console.log(err); });
}

Install Axios:

$ npm install [email protected] --save

Add the import:

import axios from 'axios';

You should now have:

import { createRoot } from 'react-dom/client';
import { Component } from 'react';
import axios from 'axios';  // new


class App extends Component {
  constructor() {
    super();
  }

  // new
  getUsers() {
    axios.get(`${process.env.REACT_APP_API_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">Users</h1>
              <hr/><br/>
            </div>
          </div>
        </div>
      </section>
    )
  }
};

const container = document.getElementById('root');
const root = createRoot(container);

root.render(<App />);

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

$ docker-compose up -d

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

$ docker-compose exec api python -m pytest "src/tests" -p no:warnings

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

$ export REACT_APP_API_SERVICE_URL=http://localhost:5004

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 call, for now, in the constructor():

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

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

Access to XMLHttpRequest at 'http://localhost:5004/users'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

In short, we're making a cross-origin AJAX request (from http://localhost:3000 to http://localhost:5004), 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.10

To keep things simple, let's allow cross origin requests on all routes, from any domain. Simply update services/users/src/__init__.py like so:

# src/__init__.py


import os

from flask import Flask
from flask_admin import Admin
from flask_cors import CORS  # new
from flask_sqlalchemy import SQLAlchemy
from werkzeug.middleware.proxy_fix import ProxyFix

# instantiate the extensions
db = SQLAlchemy()
cors = CORS()  # new
admin = Admin(template_mode="bootstrap3")


def create_app(script_info=None):

    # instantiate the app
    app = Flask(__name__)
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)

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

    # set up extensions
    db.init_app(app)
    cors.init_app(app, resources={r"*": {"origins": "*"}})  # new
    if os.getenv("FLASK_ENV") == "development":
        admin.init_app(app)

    # register api
    from src.api import api

    api.init_app(app)

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

    return app

To test, start by updating the containers:

$ docker-compose up -d --build

Then, update and seed the database:

$ docker-compose exec api python manage.py recreate_db

$ docker-compose exec api python manage.py seed_db

With both apps running, navigate to http://localhost:3000, 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_API_SERVICE_URL}/users`)
  .then((res) => { console.log(res.data); })  // new
  .catch((err) => { console.log(err); });
}

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

[
    {
        "created_date": "2020-11-14T14:16:18.033783",
        "email": "[email protected]",
        "id": 1,
        "username": "michael"
    },
    {
        "created_date": "2020-11-14T14:16:18.033783",
        "email": "[email protected]",
        "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 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.

react lifecycle

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

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

Update the component:

class App extends Component {
  // updated
  constructor() {
    super();
  };

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

  getUsers() {
    axios.get(`${process.env.REACT_APP_API_SERVICE_URL}/users`)
    .then((res) => { console.log(res.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">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_API_SERVICE_URL}/users`)
  .then((res) => { this.setState({ users: res.data }); }) // updated
  .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">Users</h1>
            <hr/><br/>
            {/* new */}
            {
              this.state.users.map((user) => {
                return (
                  <p
                    key={user.id}
                    className="box title is-4 username"
                  >{ user.username }
                  </p>
                )
              })
            }
          </div>
        </div>
      </div>
    </section>
  )
}

What's happening?

  1. We iterated over the users (from the AJAX request) and created a new <p> element for each user. 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 "services/client/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 (
            <p
              key={user.id}
              className="box title is-4 username"
            >{ user.username }
            </p>
          )
        })
      }
    </div>
  )
};

export default UsersList;

Why did we use a stateless functional component here rather than a stateful 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 stateful class-based 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 stateless functional 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">Users</h1>
            <hr/><br/>
            <UsersList users={this.state.users}/>
          </div>
        </div>
      </div>
    </section>
  )
}

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

Make sure your app looks like this in the browser:

react ajax request




Mark as Completed