TL;DR: There are several tools available for developers to aid the building of various types of websites and applications. One such tool is Create React App(CRA), the CLI tool that helps JavaScript developers create react apps with no build configuration. As awesome as CRA is, developers still need a way of tweaking, adding special scripts and modules that doesn't come bundled with CRA. Today, I'll teach you how to create custom create-react-app scripts for you and your team!


Many developers already use create-react-app to build their React applications, but like I mentioned earlier, developers are still screaming for more configuration options! Some are interested in having support for:

  • PostCSS
  • CSS Modules
  • LESS
  • SASS
  • ES7
  • MobX
  • Server Rendering

..and a lot more out of the box!

A lot of developers, including JavaScript newbies create React apps from scratch daily, so the CRA team at Facebook built the create-react-app tool to make the process of creating such apps less tedious and error-prone.

As a developer that needs support for some of the technologies I highighted earlier, one way of going about it is running npm run eject. This command copies all the config files and dependencies right into your project, then you can manually configure your app with all sorts of tools to satisfaction.

One major challenge developers might face with eject is not been able to enjoy the future features of CRA . Another challenge with eject would be ineffecient synchronised setup across React developers working in team. One great way of solving this later challenge is publishing a fork of react-scripts for your team, then all your developers can just run create-react-app my-app --scripts-version mycompany-react-scripts and have the same setup across board. Let's learn how to accomplish that!

Create a Fork

Open up your GitHub repo and fork the create-react-app repo

Creating a fork of create-react-app Creating a fork of create-react-app

Note: It is recommended that you fork from the latest stable branch. Master is unstable.

Inside the packages directory, there is a folder called react-scripts. The react-scripts folder contains scripts for building, testing and starting your app. In fact, this is where we can tweak, configure and add new scripts and templates.

Tweak the Configuration

Clone the directory and open up the react-scripts/scripts/init.js in your code editor. Let's add some few console messages like so:


......
......
console.log(chalk.red('VERY IMPORTANT:'));
console.log('Create a .env file at the root of your project with REACT_APP_EMPLOYEE_ID and REACT_APP_POSITION_ID');
console.log('  You can find these values in the company dashboard under application settings.');
console.log('  https://company.bamboohr.com/settings');
console.log();
.......

Block to add important message Add the important message during installation here

Important Message added to Installation process Added important message to show during installation

Now, Let's change templates

Open up react-scripts/template/src/App.js and replace it with this:


import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {

  getEnvValues() {
    if (!process.env.REACT_APP_EMPLOYEE_ID || !process.env.REACT_APP_POSITION_ID) {
      throw new Error('Please define `REACT_APP_EMPLOYEE_ID` and `REACT_APP_POSITION_ID` in your .env file');
    }

    const employeeID = process.env.REACT_APP_EMPLOYEE_ID
    const position = process.env.REACT_APP_POSITION_ID;

    return { employeeID, position };
  }

  render() {

    const { employeeID, position } = this.getEnvValues();

    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to Unicode Labs</h2>
        </div>
        <p className="App-intro">
           <b> Employee ID: { employeeID } </b><br/><br/>
           <b> Position: { position } </b>
        </p>
      </div>
    );
  }
}

export default App;

Now, go to react-scripts/template/public directory. Open the index.html file and change the value of the <title> tag to Unicode Labs.

You can also change the favicon to your company's favicon. You can change as many things as you want and add custom components that your team uses frequently.

Create an .env.example in the react-scripts/template directory that contains the following:


REACT_APP_EMPLOYEE_ID='44566'
REACT_APP_POSITION_ID='ENGR'

A user will have to rename it to .env once the create-react-app tool is done installing the react-scripts. You should add this instruction to the README file.

Note: CRA already includes support for custom env variables if you're open to prefixing their names with REACT_APP.

That's all we need!

Publish react-scripts to NPM

Before publishing to npm, we need to change the value of the name key of the package.json file in react-scripts directory to unicodelabs-react-scripts.

Change the value of the description key to Unicodelabs Configuration and scripts for Create React App. Also, point the value of the repository key to the right location. In my case, it is unicodelabs/create-react-app.

Now, cd to the react-scripts directory from your terminal like so:

react-scripts directory Change into this directory on your terminal

You need to login to npm like so:

Npm Login Log into Npm

Go ahead and publish

Publish Published unicodelabs-react-scripts to npm

Test Your Custom Script

Head over to your terminal and run:


create-react-app test-app --scripts-version unicodelabs-react-scripts

In your own case it would be yourname-react-scripts, where yourname is your company name or whatever name you choose to give it.

CRA would install it and then you will see a notice like so:

Important Warning Important Warning

Remember, when we put this message in the code earlier? Awesome!

Now, cd into the test-app directory, rename the .env.example to .env and run npm start command.

Your app will spin up with the new template like so:

New template showing up

Note: If you have yarn installed, then create-react-app would install your app using Yarn.

Aside: Authenticate a React App with Auth0

We can protect our applications and APIs so that only authenticated users can access them. Let's explore how to do this with a React application using Auth0.

Auth0 login screen

We'll need an Auth0 account to manage authentication. To sign up for a free account, we can follow this link. Next, let's set up an Auth0 client app and API so Auth0 can interface with a React App.

Setting Up a Client App

  1. Let's go to our Auth0 Dashboard and click the "create a new client" button.
  2. Let's call our app as "React Demo" and select "Single Page Web Applications".
  3. In the Settings for our new Auth0 client app, let's add http://localhost:3000/callback to the Allowed Callback URLs.
  4. If desired, we can set up some social connections. We can then enable them for our app in the Client options under the Connections tab. The example shown in the screenshot above utilizes username/password database, Facebook, Google, and Twitter. For production, make sure to set up the correct social keys and do not leave social connections set to use Auth0 dev keys.

Set Up an API

  1. Go to APIs in your Auth0 dashboard and click on the "Create API" button. Enter a name for the API. Set the Identifier to your API endpoint URL. In this example, this is http://localhost:3001/api/. The Signing Algorithm should be RS256.
  2. You can consult the Node.js example under the Quick Start tab in your new API's settings. We'll implement our Node API in this fashion, using Express, express-jwt, and jwks-rsa.

We're now ready to implement Auth0 authentication on both our React client and Node backend API.

Dependencies and Setup

There are only two dependencies that we really need to install: auth0.js and history. To do that, let's issue npm install --save auth0-js history in the project root.

Note: As we want the best security available, we are going to rely on the Auth0 login page. This method consists of redirecting users to a login page hosted by Auth0 that is easily customizable right from the Dashboard.

After installing it, we can create an authentication service to interface with the auth0.js script. Let's call this service Auth and create it in the src/Auth/ directory with the following code:

import history from '../history';
import auth0 from 'auth0-js';

export default class Auth {
  auth0 = new auth0.WebAuth({
    // the following three lines MUST be updated
    domain: 'bkrebs.auth0.com',
    audience: 'https://bkrebs.auth0.com/userinfo',
    clientID: '3co4Cdt3h3x8En7Cj0s7Zg5FxhKOjeeK',
    redirectUri: 'http://localhost:3000/callback',
    responseType: 'token',
    scope: 'openid'
  });

  constructor() {
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
  }

  handleAuthentication() {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken) {
        this.setSession(authResult);
        history.replace('/home');
      } else if (err) {
        history.replace('/home');
        console.log(err);
      }
    });
  }

  setSession(authResult) {
    // Set the time that the access token will expire at
    let expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('expires_at', expiresAt);
    // navigate to the home route
    history.replace('/home');
  }

  login() {
    this.auth0.authorize();
  }

  logout() {
    // Clear access token and expiration from local storage
    localStorage.removeItem('access_token');
    localStorage.removeItem('expires_at');
    // navigate to the home route
    history.replace('/home');
  }

  isAuthenticated() {
    // Check whether the current time is past the
    // access token's expiry time
    let expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return new Date().getTime() < expiresAt;
  }
}

The Auth service just created contains functions to deal with various steps of the sign in/sign up process. The following list briefly summarizes these functions and their descriptions:

  • handleAuthentication: looks for the result of the authentication in the URL hash. Then, process the result with the parseHash method from auth0-js;
  • setSession: sets the user's access token and the access token's expiry time;
  • login: initiates the login process, redirecting users to the login page;
  • logout: removes the user's tokens and expiry time from browser storage;
  • isAuthenticated: checks whether the expiry time for the user's access token has passed;

Besides these functions, the class contains a field called auth0 that is initialized with values extracted from the Auth0 client. Let's keep in mind that we need to update them accordingly before proceeding.

Attentive readers probably noticed that the Auth service also imports a module called history that we haven't talked about. We can define this module in only two lines, but let's define it in a file to provide reusability. Let's call this file ./src/history/history.js and add the following code:

import createHistory from 'history/createBrowserHistory'

export default createHistory()

After creating both elements, we can refactor our App component to make use of the Auth service.

import React, { Component } from 'react';
import { Navbar, Button } from 'react-bootstrap';
import './App.css';

class App extends Component {
  goTo(route) {
    this.props.history.replace(`/${route}`)
  }

  login() {
    this.props.auth.login();
  }

  logout() {
    this.props.auth.logout();
  }

  render() {
    const { isAuthenticated } = this.props.auth;

    // ... render the view
  }
}

export default App;

Note that we are passing this service through props. Therefore, when including the App component, we need to inject Auth into it: <App auth={auth} />.

Considering that we are using the Auth0 login page, our users are taken away from the application. However, after they authenticate, users automatically return to the callback URL that we set up previously (http://localhost:3000/callback). This means that we need to create a component responsible for this URL:

import React, { Component } from 'react';
import loading from './loading.svg';

class Callback extends Component {
  render() {
    const style = //...

    return (
      <div style={style}>
        <img src={loading} alt="loading"/>
      </div>
    );
  }
}

export default Callback;

This component can just contain a loading indicator that keeps spinning while the application sets up a client-side session for the users. After the session is set up, we can redirect users to another route.

Please refer to the official Quick Start Guide to see, step by step, how to properly secure a React application. Besides the steps shown in this section, the guide also shows:

Conclusion

Great programmers constantly sharpen their tools daily to increase productivity. CRA is a great tool for quickly building React Applications. In addition, having your own customized fork of react-scripts helps you and your team easily add all the configurations you need. You'll need to maintain your fork, and make sure it is synced with the upstream to have all updates. Backstroke is a bot that can help you with this.

Have a very productive time hacking away!