ReactJS 16 shipped with a lot of new features. Since then, even more features have been introduced to the library.

React 16.3 ships with a few major changes that I'll like to highlight in this article. Let's dive in!

StrictMode Component

JavaScript developers are very familiar with the strict keyword. This keyword keeps you in check while developing your apps and raises an alarm during development to let you know about potential problems in your codebase.

React 16.3 ships with a StrictMode component that highlights potential problems in your ReactJS codebase.

This component runs a check in development mode to determine if there are issues with the descendant components such as using an unsafe lifecycle method, legacy ref API, etc.

import React from 'react';

function WokeAlarm() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <SetAlarm />
          <RingAlarm />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}

New Lifecycle Methods

React 16.3 ships with new lifecycle methods such as getDerivedStateFromProps, and getSnapshotBeforeUpdate.

The current lifecycle methods componentWillMount, componentWillReceiveProps, and componentWillUpdate will be deprecated in a future ReactJS 16.x release because they have been known to be problematic and behave in unintended ways. These methods will continue to be available for use in the next major release, React 17.

  • getDerivedStateFromProps can be used instead of componentWillReceiveProps.
  • componentDidMount can be used instead of componentWillMount.
  • componentDidUpdate can be used instead of componentWillUpdate.

The new getDerivedStateFromProps method is static and will be called on the initial mounting of the component and also when the component is re-rendered.

"The new getDerivedStateFromProps method is static and will be called on the initial mounting of the component."

class Speaker extends Component {
  static getDerivedStateFromProps() {
    if (nextProps.value !== prevState.value) {
      return ({ value: nextProps.value });
    }
  }
}

The new getSnapshotBeforeUpdate method is called before any DOM mutations happen. It's great to perform any sort of calculations needed for your component here and then pass it to componentDidUpdate as the third argument like so:

"The new getSnapshotBeforeUpdate method is called before any DOM mutations happen."

...
getSnapShotBeforeUpdate(prevProps, prevState) {
  return prevProps.list.length < this.props.list.length ? this.listRef.scrollHeight : null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot !== null) {
    const listRef = this.listRef;
    listRef.scrollTop += listRef.scrollHeight - snapshot;
  }
}
...

forwardRef

Refs provide a way to access ReactJS elements or DOM nodes created in the render method. They are great for getting values from input elements, working with third-party DOM libraries, et al. However, there were some challenges with refs regarding component encapsulation.

forwardRef automatically passes a ref received by a parent component to its children. It's great for reusable components in component libraries. As the name implies, the component is forwarding the ref to its child.

Check out the example below:

import React, { Component } from 'react';

const LoginButton = React.forwardRef((props, ref) => (
  <button ref={ref} class="Login"> {props.children} </button>
));


class Display extends Component {
  myRef = React.createRef();

  componentDidMount() {
    this.myRef.current.focus();
  }

  render() {
    return (
      <div className="container">
        <LoginButton ref={this.myRef}> Get In! </LoginButton>
      </div>
    ) 
  }
} 

The use of forwardRef is more valuable in Higher Order Components. The ReactJS blog has the perfect example of this scenario.

createRef

Previously, ReactJS developers had two ways of using refs: You either made use of the callback API or the legacy string ref API.

With React 16.3, you can make use of the createRef API for managing refs without any negative implications. It's simpler and developer friendly too. Check out the example below:

without createRef API

class SpeakerComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <input type="text" ref={(input) => {
      this.inputRef = input;
    }} />;
  }
}

with createRef API

class SpeakerComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}

Context API

The Context API has been available for a while but in experimental mode. React 16.3 ships with an ergonomic Context API that supports static type checking and deep updates. This API solves the challenge most developers experience which is the complexity of passing data from child to parent and back and make them quickly reach out for Redux.

Check out this example below:

import React, { Component } from 'react';

const Dog = (props) => (
  <div>
    <Animal name={props.name} />
  </div>
);

}
class Animal extends Component {
  render() {
    return (
      <div>
        <p> Hey, I'm a {this.props.name} </p>
      </div>
    )
  }
}


class App extends Component {
  state = {
    name: 'Casanova',
    food: 'bone',
  }

  render() {
    return (
      <div>
        <Dog name={this.state.name} />
      </div>
    )
  }
}

export default App;

This is a simple example, but the way we pass data from component to component with the use of props is not developer friendly and could get out of hand very quickly! At this point, most developers quickly reach out for a data store or state management library to manage this process efficiently. However, the new Context API in React 16.3 can be used to eliminate this challenge.

With this new API, we'll need a Provider and a Consumer. The data will live in the Provider while the Consumer represents where the data needs to be accessed.

import React, { Component } from 'react';

// create a new context
const MyContext = React.createContext();

// create a provider component
class MyProvider extents Component {
  state = {
    name: 'Casanova',
    food: 'bone'
  }

  render() {
    return (

      <MyContext.Provider value={{ state: this.state }}>
        { this.props.children }
      </MyContext.Provider>

    )
  }
}

const Dog = (props) => (
  <div>
    <Animal />
  </div>
);

class Animal extends Component {
  render() {
    return (
      <div>
        <MyContext.Consumer>
          {(context) => (
            <React.Fragment>
              <h3> Food: { context.state.food } </h3>
              <h3> Name: { context.state.name } </h3>
            </React.Fragment>
          )}
        </MyContext.Consumer>
      </div>
    )
  }
}

class App extends Component {
  render() {
    return (
      <MyProvider>
        <div>
          <Dog />
        </div>
      </MyProvider>
    )
  }
}

export default App;

In the code above, there is a provider, <MyProvider />, that houses the state and renders a Context provider.

The Context provider provides the ability to render children components as evident in the <App /> component. We simply called the consumer, <MyContext.Consumer />, in the Animal component to render the data we needed. This is more organized, and avoids the case of props drilling hell!

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 application and API so Auth0 can interface with a React App.

Setting Up an Auth0 Application

  1. Let's go to our Auth0 Dashboard and click the "create a new application" button.
  2. Let's call our app as "React Demo" and select "Single Page Web Applications".
  3. In the Settings for our new Auth0 application, 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 Application 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 application. 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

React 16 has been on a roller-coaster. And it's amazing how ReactJS is becoming more of a way of life than a library, in my opinion.

Have you upgraded to React 16.3 yet? What are your thoughts? Let me know in the comments section! 😊