developers

React 16 Release: What’s New?

ReactJS 16 brings major changes to the popular JavaScript library for building user interfaces. Learn what's new in ReactJS!

Oct 26, 201711 min read


TL;DR: ReactJS is a UI library that has gained massive adoption by developers and organizations around the world because of its efficient and reactive model. In this article, we'll highlight notable additions to ReactJS 16 and dabble into the architecture it runs on.


ReactJS is a JavaScript library, built and maintained by Facebook. At the time of writing, ReactJS has over 78,000 stars on GitHub. And many web platforms such as Twitter, Airbnb, Lyft, Dropbox, Pinterest, Whatsapp and Instagram use ReactJS to build their user interfaces. The ReactJS developer community is very robust. In fact, the community is so robust that when Facebook decided to implement the BSD+ Patents license, there was a public outcry from the community. The complaints from developers were on every other blog and forum as well as at meetups and conferences. Developers and software shops around the world are pretty excited and relieved that ReactJS has been re-licensed under the MIT license.

ReactJS 16 was announced to the world on September 26, 2017. Facebook has been working on releasing this new version for a while now. It's great that there is a new release, but ReactJS 16 is a very special release. What's exciting about this new ReactJS release is the fact that it was entirely rewritten from scratch while ensuring that the public API remains unchanged. This major release brings a lot of new features, deprecations, and changes.

ReactJS API-Compatible Rewrite - Why?

Questions have been flying around as to why Facebook decided to rewrite the internals of ReactJS while keeping the public API essentially unchanged. Facebook has been working on React Fiber, which is a reimplementation of ReactJS's core algorithm for about two years now. The goal of React Fiber is to enable incremental rendering—the ability to split rendering work into chunks and spread it out over multiple frames. In addition, the ability to pause, abort, or reuse work as new updates come in and the ability to assign priority to different types of updates. There are many reasons as to why it was rewritten but I'll highlight a few reasons.

The goal of React Fiber is to enable incremental rendering.

Tweet This
  1. ReactJS needed a way to support asynchronous rendering. The current architecture couldn't have handled this addition so it needed to be re-designed from the ground up.

  2. Support for graceful and efficient error handling using error boundaries in ReactJS. It was difficult to add this frequently-demanded feature to the previously existing ReactJS architecture, so it needed to be re-designed from the ground up.

  3. The need for a new foundation that is immensely extensible and powerful enough to accommodate new idea implementation going forward. Furthermore, the support for longstanding features such as having fragments, returning text from render, unification of the scheduling of different subtrees and simple, non-complex ways to write custom renderers for ReactJS.

React Fiber

Let's talk a bit about React Fiber. As I mentioned earlier, React Fiber has been in development for a while. And there was a website made specially just to track the progress of React Fiber; isfiberreadyyet.com.

React Fiber is a complete, backward compatible rewrite of the ReactJS core which enables sophisticated scheduling of rendering work. It is a reimplementation of ReactJS's core algorithm. The goal of React Fiber is to increase its suitability for app development in sections like gestures, animation, and layouts. One key feature is the support for incremental rendering. Incremental rendering is the ability to split the rendering process into chunks and spread it out over multiple frames.

React Fiber is a reimplementation of a stack frame specialized for ReactJS components. Each fiber can be thought of as a virtual stack frame where information from the frame is preserved in memory on the heap, and because the info is saved on the heap, you can control and play with the data structures and process the relevant information as needed.

React Fiber allows smooth rendering of the UI by pausing once in a while and checking for more important updates rather than waiting for all the changes to be propagated throughout the entirety of the component tree before updating the UI. The task scheduling ability of React Fiber makes this possible.

In ReactJS, we have two important players: the Reconciler and the Renderer. The Renderer is a pluggable section of ReactJS that allows rendering of the UI to happen across and outside the DOM. It was originally created for the DOM but was later adapted to support native platforms. ReactJS has the ReactJS DOM Renderer - renders ReactJS components to the DOM, ReactJS Native Renderer - renders ReactJS components to native views, and the ReactJS Test Renderer - renders ReactJS components to JSON trees.

The Reconciler is used by the renderer to perform updates to the DOM. Whenever a component updates, be it mounting, unmounting or any form of update, the reconciler (known as the stack reconciler) processes the component tree from top to bottom synchronously in a single pass, checks for changes in the tree, then passes on these changes to the renderer. In previous versions of ReactJS, the reconciler did not have the ability to pause work, thus making performance suboptimal when deep updates occur and the CPU time is limited.

In ReactJS, we have two important players: the Reconciler and the Renderer.

Tweet This

With React Fiber, the new reconciler has the ability to do the following:

  • Split interruptible work into chunks.
  • Pause work and come back to it later.
  • Reuse previously completed work.
  • And abort work.

Note: Work is the result of an update. Updates are computations performed during the traversal of the component trees.

Check out more information on Reconciliation and the React Fiber Architecture.

ReactJS 16 Features

Apart from the fact that ReactJS 16 runs on the new engine, React Fiber, ReactJS 16 ships with some new features.

  • Error handling using Error boundaries: In ReactJS 16, the error handling has been greatly improved. Before now, ReactJS applications broke whenever a runtime error occurred and required a page refresh to recover from the broken state. The component is unmounted each time any error throw in the constructor, render method and lifecycle methods. In ReactJS 16, error boundaries have been introduced to capture errors and display a fallback UI instead of unmounting the component every time. Error boundaries are simply ReactJS components that catch JavaScript errors anywhere in a component and child trees, log those errors and display a fallback UI. It is important to know that error boundaries catch errors thrown in the constructor, render and lifecycle methods. The new lifecycle hook method that makes a class component an error boundary is

    componentDidCatch(error, info)
    . This method works like a try/catch block for components. Create an error boundary component once and call it everywhere it's needed in your application.

      class ErrorBoundary extends React.Component {
        constructor(props) {
          super(props);
          this.state = { hasError: false };
        }
    
        componentDidCatch(error, info) {
          // Display fallback UI
          this.setState({ hasError: true });
          // You can also log the error to an error reporting service
          logErrorToMyService(error, info);
        }
    
        render() {
          if (this.state.hasError) {
            // You can render any custom fallback UI
            return <h1>Something went wrong.</h1>;
          }
          return this.props.children;
        }
      }
  • Support for Defining Custom DOM Attributes: Before now, if you used a custom DOM attribute, ReactJS would skip it. In ReactJS 16, unknown attributes will be passed onto the DOM. A typical example is shown below:

      <div alien="skullIsland" />
    
      // will be displayed in ReactJS 15 as:
      <div />
      <div alien="skullIsland" />
    
      // will be displayed in ReactJS 16 as:
      <div alien="skullIsland" />

Note: Using the canonical ReactJS naming for known attributes still remain the same.

  • Fragments and Strings as new render return types: In ReactJS 16, you can now return an array of multiple elements from a component's render method.

      render() {
        // No need to wrap items in an extra element!
        return [
          // Don't forget the keys :)
          <p key="first">ReactJS</li>,
          <span key="second">PreactJS</li>,
          <span> key="third">VueJS</li>
        ];
      }

    Returning arrays

      render() {
        return 'Boss, I just returned a string. Can you believe it? ReactJS 16  is dope!';
      }

    Returning a string

  • Portals: This is a concept allows you to render children into a DOM node that exists outside of the hierarchy of the parent component. In layman terms, it simply means a child can be inserted into a different location in the DOM via Portals. For example, your app has a section component with a paragraph element as its child. Portals can be used to make the paragraph child element break out of its container.

        ReactDOM.createPortal(child, container);

    Render the child into the container DOM node

      render() {
        return ReactDOM.createPortal(
          this.props.children,
          domNode,
        );
      }

    A typical example

  • Improved server-side rendering: The server renderer was completely rewritten in ReactJS 16 to be very fast. In addition, server-side rendering in ReactJS 16 is about three times faster than ReactJS 15 because the server renderer supports streaming. This makes sending of data from the server to the client faster than usual. In ReactJS 16, there are two different methods for rendering on the client side,

    render()
    and
    hydrate()
    .
    render()
    as we already know for rendering content solely on the client side,
    hydrate()
    for rendering on top of server-side rendered markup. Furthermore, ReactJS 16 is better at hydrating server-rendered HTML once it reaches the client. It no longer requires the initial render to exactly match the result from the server. Instead, it will attempt to reuse as much of the existing DOM as possible.

In ReactJS 16, there are two different methods for rendering on the client side, render() and hydrate().

Tweet This
```js
  import { hydrate } from "react-dom"
  import Profile from "./Profile"
  hydrate(<Profile />, document.getElementById("profile-container"));
```

Note:

render()
can still be used to render on top of server-side rendered markup, but it's recommended to use
hydrate()
now for that type of rendering in ReactJS 16.

ReactJS 16 vs ReactJS 15 server-side rendering Source: hackernoon.com

ReactJS 16 does not support error boundaries and portals in server-side rendering.

For more information, check out this excellent article on server-side rendering in ReactJS 16.

ReactJS 16 Deprecations and Breaking Changes

There a few deprecations and a number of breaking changes in ReactJS 16.

  • Discontinued support for React Add-ons.
  • Calling
    setState
    with null no longer triggers an update.
  • Calling
    setState
    directly inside the
    render()
    method always causes an update.
  • setState
    callbacks now fire immediately after
    componentDidMount
    or
    componentDidUpdate
    .
  • The
    componentDidUpdate
    lifecycle no longer accepts the
    prevContext
    parameter.
  • ReactDOM.render()
    and
    ReactDOM.unstable_renderIntoContainer()
    now return
    null
    if called from inside a lifecycle method.
  • Previously, changing the ref to a component would always detach the ref before that component's render was called. Now, we change the ref later, when applying the changes to the DOM.
  • As I mentioned earlier, hydrating a server-rendered container now has an explicit API. Use
    ReactDOM.hydrate
    instead of
    ReactDOM.render
    if you're reviving server rendered HTML. Keep using
    ReactDOM.render
    if you're just doing the client-side rendering.

For more information, check out the full list of deprecations and breaking changes on GitHub.

There has been a lot of performance observation and testing done by apps that have made the upgrade to ReactJS 16. And so far, there have been a lot of cheer and positive feedback for ReactJS 16. In fact, Twitter Lite already uses ReactJS 16.

The Twitter Lite engineering team discovered that the app's bundle size reduced a little and there were very clear improvements on large trees.

Aside: Securing React Apps with Auth0

As you will learn in this section, you can easily secure your React applications with Auth0, a global leader in Identity-as-a-Service (IDaaS) that provides thousands of enterprise customers with modern identity solutions. Alongside with the classic username and password authentication process, Auth0 allows you to add features like Social Login, Multifactor Authentication, Passwordless Login, and much more with just a few clicks.

To follow along the instruction describe here, you will need an Auth0 account. If you don't have one yet, now is a good time to sign up for a free Auth0 account.

Also, if you want to follow this section in a clean environment, you can easily create a new React application with just one command:

npx create-react-app react-auth0

Then, you can move into your new React app (which was created inside a new directory called

react-auth0
by the
create-react-app
tool), and start working as explained in the following subsections.

Setting Up an Auth0 Application

To represent your React application in your Auth0 account, you will need to create an Auth0 Application. So, head to the Applications section on your Auth0 dashboard and proceed as follows:

  1. click on the Create Application button;
  2. then define a Name to your new application (e.g., "React Demo");
  3. then select Single Page Web Applications as its type.
  4. and hit the Create button to end the process.

After creating your application, Auth0 will redirect you to its Quick Start tab. From there, you will have to click on the Settings tab to whitelist some URLs that Auth0 can call after the authentication process. This is a security measure implemented by Auth0 to avoid the leaking of sensitive data (like ID Tokens).

So, when you arrive at the Settings tab, search for the Allowed Callback URLs field and add

http://localhost:3000/callback
into it. For this tutorial, this single URL will suffice.

That's it! From the Auth0 perspective, you are good to go and can start securing your React application.

Dependencies and Setup

To secure your React application with Auth0, there are only three dependencies that you will need to install:

  • auth0.js
    : This is the default library to integrate web applications with Auth0.
  • react-router
    : This is the de-facto library when it comes to routing management in React.
  • react-router-dom
    : This is the extension to the previous library to web applications.

To install these dependencies, move into your project root and issue the following command:

npm install --save auth0-js react-router react-router-dom

Note: As you want the best security available, you 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 your Auth0 dashboard. If you want to learn why this is the best approach, check the Universal vs. Embedded Login article.

After installing all three libraries, you can create a service to handle the authentication process. You can call this service

Auth
and create it in the
src/Auth/
directory with the following code:

// src/Auth/Auth.js
import auth0 from 'auth0-js';

export default class Auth {
  constructor() {
    this.auth0 = new auth0.WebAuth({
      // the following three lines MUST be updated
      domain: '<AUTH0_DOMAIN>',
      audience: 'https://<AUTH0_DOMAIN>/userinfo',
      clientID: '<AUTH0_CLIENT_ID>',
      redirectUri: 'http://localhost:3000/callback',
      responseType: 'token id_token',
      scope: 'openid profile',
    });

    this.getProfile = this.getProfile.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.setSession = this.setSession.bind(this);
  }

  getProfile() {
    return this.profile;
  }

  handleAuthentication() {
    return new Promise((resolve, reject) => {
      this.auth0.parseHash((err, authResult) => {
        if (err) return reject(err);
        console.log(authResult);
        if (!authResult || !authResult.idToken) {
          return reject(err);
        }
        this.setSession(authResult);
        resolve();
      });
    });
  }

  isAuthenticated() {
    return new Date().getTime() < this.expiresAt;
  }

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

  logout() {
    // clear id token and expiration
    this.idToken = null;
    this.expiresAt = null;
  }

  setSession(authResult) {
    this.idToken = authResult.idToken;
    this.profile = authResult.idTokenPayload;
    // set the time that the id token will expire at
    this.expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
  }
}

The

Auth
service that you just created contains functions to deal with different steps of the sign in/sign up process. The following list briefly summarizes these functions and what they do:

  • getProfile
    : This function returns the profile of the logged-in user.
  • handleAuthentication
    : This function looks for the result of the authentication process in the URL hash. Then, the function processes the result with the
    parseHash
    method from
    auth0-js
    .
  • isAuthenticated
    : This function checks whether the expiry time for the user's ID token has passed.
  • login
    : This function initiates the login process, redirecting users to the login page.
  • logout
    : This function removes the user's tokens and expiry time.
  • setSession
    : This function sets the user's ID token, profile, and expiry time.

Besides these functions, the class contains a field called

auth0
that is initialized with values extracted from your Auth0 application. It is important to keep in mind that you have to replace the
<AUTH0_DOMAIN>
and
<AUTH0_CLIENT_ID>
placeholders that you are passing to the
auth0
field.

Note: For the

<AUTH0_DOMAIN>
placeholders, you will have to replace them with something similar to
your-subdomain.auth0.com
, where
your-subdomain
is the subdomain you chose while creating your Auth0 account (or your Auth0 tenant). For the
<AUTH0_CLIENT_ID>
, you will have to replace it with the random string copied from the Client ID field of the Auth0 Application you created previously.

Since you are using the Auth0 login page, your users are taken away from the application. However, after they authenticate, users automatically return to the callback URL that you set up previously (i.e.,

http://localhost:3000/callback
). This means that you need to create a component responsible for this route.

So, create a new file called

Callback.js
inside
src/Callback
(i.e., you will need to create the
Callback
directory) and insert the following code into it:

// src/Callback/Callback.js
import React from 'react';
import { withRouter } from 'react-router';

function Callback(props) {
  props.auth.handleAuthentication().then(() => {
    props.history.push('/');
  });

  return <div>Loading user profile.</div>;
}

export default withRouter(Callback);

This component, as you can see, is responsible for triggering the

handleAuthentication
process and, when the process ends, for pushing users to your home page. While this component processes the authentication result, it simply shows a message saying that it is loading the user profile.

After creating the

Auth
service and the
Callback
component, you can refactor your
App
component to integrate everything together:

// src/App.js

import React from 'react';
import { withRouter } from 'react-router';
import { Route } from 'react-router-dom';
import Callback from './Callback/Callback';
import './App.css';

function HomePage(props) {
  const { authenticated } = props;

  const logout = () => {
    props.auth.logout();
    props.history.push('/');
  };

  if (authenticated) {
    const { name } = props.auth.getProfile();
    return (
      <div>
        <h1>Howdy! Glad to see you back, {name}.</h1>
        <button onClick={logout}>Log out</button>
      </div>
    );
  }

  return (
    <div>
      <h1>I don't know you. Please, log in.</h1>
      <button onClick={props.auth.login}>Log in</button>
    </div>
  );
}

function App(props) {
  const authenticated = props.auth.isAuthenticated();

  return (
    <div className="App">
      <Route
        exact
        path="/callback"
        render={() => <Callback auth={props.auth} />}
      />
      <Route
        exact
        path="/"
        render={() => (
          <HomePage
            authenticated={authenticated}
            auth={props.auth}
            history={props.history}
          />
        )}
      />
    </div>
  );
}

export default withRouter(App);

In this case, you are actually defining two components inside the same file (just for the sake of simplicity). You are defining a

HomePage
component that shows a message with the name of the logged-in user (that is, when the user is logged in, of course), and a message telling unauthenticated users to log in.

Also, this file is making the

App
component responsible for deciding what component it must render. If the user is requesting the home page (i.e., the
/
route), the
HomePage
component is shown. If the user is requesting the callback page (i.e.,
/callback
), then the
Callback
component is shown.

Note that you are using the

Auth
service in all your components (
App
,
HomePage
, and
Callback
) and also inside the
Auth
service. As such, you need to have a global instance for this service, and you have to include it in your
App
component.

So, to create this global

Auth
instance and to wrap things up, you will need to update your
index.js
file as shown here:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Auth from './Auth/Auth';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

const auth = new Auth();

ReactDOM.render(
  <BrowserRouter>
    <App auth={auth} />
  </BrowserRouter>,
  document.getElementById('root'),
);
registerServiceWorker();

After that, you are done! You just finished securing your React application with Auth0. If you take your app for a spin now (

npm start
), you will be able to authenticate yourself with the help of Auth0, and you will be able to see your React app show your name (that is, if your identity provider does provide a name).

If you are interested in learning more, please, refer to the official React 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

ReactJS is an awesome front-end library to employ in building your user interfaces. It is faster now because it runs on React Fiber and allows you to build more performant, smoothe UIs for your web and native applications.

ReactJS 16 came loaded with lots of new features and significant improvements. Kudos to the ReactJS team and the JavaScript open source community for all their efforts in making ReactJS a better tool.

Have you switched to ReactJS 16 yet? What are your thoughts? Let me know in the comments section! 😊