7 things about React 16 your team should know

7-things-about-react-16-header.png

These are some of the most interesting features about React 16 that you should take note of.

Introduction

React 16, also known as React Fiber, is a complete rewrite of the popular library’s core algorithm aimed at improving perceived performance and responsiveness of complex applications.

Some of the main goals of React 16 are: the faster rendering of components and animations, the ability to prioritize DOM updates, the ability to split rendering work into chunks and spread it out over multiple frames, and improved suitability for gestures.

This rewrite has been in the works for a couple of years and only reached a beta release stage in July 2017 which suggests an official public release is not too far away.

If you use React in your Front-End stack, you must be curious about the new features and what the new architecture means for your existing applications. In this article, we explore the highlights of Fiber that your team needs to know before upgrading.

React 16 is backwards compatible

Unlike the rewrite of Angular.js, for example, React 16 maintains the exact same public API as in the previous versions (15.x and earlier) so it’s not like you have to re-learn the whole thing from scratch. As with most updates to the library, there are some breaking changes but these are manageable and shouldn’t cause any serious problems.

Dan Abramov explained that upgrading to React 16 shouldn’t cause much trouble for most existing React applications, citing Facebook’s upgrade path as proof of its backwards compatibility (emphasis mine):

React 16 (work in progress) is a rewrite, but it has the same public API. Out of more than 30,000 (!) components at Facebook, only a dozen or so needed changes, and those few components were relying on unsupported or undocumented behavior. So this is quite literally 99,9% compatibility. This makes us confident React 16 will most likely work with your code too.

At the time of writing, isfiberreadyyet.com shows that 100% of the test suites pass so now is a good time to start learning what React 16 brings to the table.

Asynchronous rendering

One of the most important changes in React 16 is the introduction of a new reconciliation algorithm which is what brings about the improvements in perceived performance and responsiveness alluded to earlier in this article.

Reconciliation (a.k.a Virtual DOM) describes the process by which React finds the differences between the previous and next state of your application and makes any necessary changes before re-rendering the real DOM.

In the previous versions of React, the main thread was blocked while updates are being processed which caused some applications not to feel fluid. This is because React used a synchronous and recursive way of processing updates (now known as stack reconciliation) in which all updates must be completed entirely before returning to main thread.

The new method of reconciliation, (called fiber reconciliation) allows updates to be rendered asynchronously by breaking up the work into chunks, and committing changes only when everything is ready. This means that React can pause rendering frequently to check if the main thread has any more updates that need to be performed.

Right now, many of the performance improvements in Fiber may not be immediately observable in your applications as the initial 16.0 release is mostly focused on compatibility with existing applications, so updates are still processed in a synchronous manner, much like in the 15.x releases.

The React team has already stated that they will provide an opt-in to asynchronous rendering mode later in the 16.x release cycle.

Concurrency

Concurrency allows React to prioritize updates by importance. Under the hood, each update has an assigned priority level which determines the order in which it is processed. This allows updates with higher priority levels to interrupt low priority work even if the low priority work has already started.

React 16 priority levels as illustrated by Lin Clark

The consequence of this is that your UI feels more fluid and responsive as a result of high priority updates, such as anything that requires immediate feedback, taking precedence over other tasks that can wait some extra milliseconds (such as rendering off-screen elements).

Error Boundaries

Error boundaries are introduced in React 16 to solve the problem where an error in a component messes up React’s internal state, causing it to display puzzling errors on subsequent renders.

Any class component can become an error boundary so long as it defines a new lifecycle method componentDidCatch(error, info).

The main benefit of error boundaries is that they allow you to provide a better user experience when something goes wrong by equipping you to handle runtime errors properly within components and display a fallback interface to the user (instead of a broken UI).

For example:

1/** Credit - Dan Abramov: https://facebook.github.io/react/blog/2017/07/26/error-handling-in-react-16.html **/
2    class ErrorBoundary extends React.Component {
3      constructor(props) {
4        super(props);
5        this.state = { hasError: false };
6      }
7
8      componentDidCatch(error, info) {
9        // Display fallback UI
10        this.setState({ hasError: true });
11        // You can also log the error to an error reporting service
12        logErrorToMyService(error, info);
13      }
14
15      render() {
16        if (this.state.hasError) {
17          // You can render any custom fallback UI
18          return <h1>Something Happened</h1>;
19        }
20        return this.props.children;
21      }
22    }

Then you can use it as a regular component:

1<ErrorBoundary>
2      <Table />
3    </ErrorBoundary>

At the same time, the introduction of error boundaries implies that the presence of uncaught errors in a component is tantamount to the unmounting of the whole React component tree.

Uncaught errors in your application results in the unmounting of the whole component tree

Note that an error boundary can only catch errors in its child components, but errors within itself are propagated to the closest error boundary above it in the component hierarchy.

You can also add as many error boundaries as you wish while building your application. A sensible approach is to wrap the content of various widgets in your application into separate error boundaries so that if crashes occur in anyone of them, the rest remain functional.

It is advised that you use an error reporting service so that you can fix any errors as they occur in production. This article explains how you can use Pusher to monitor uncaught exceptions in realtime when they are thrown.

Component Stack Traces

Another useful mechanism that will make debugging React apps even easier is the inclusion of component stack traces which shows you the exact component in the hierarchy where failure has occurred.

Component stack trace

This feature is enabled by default in projects bootstrapped with Create React App and it is intended for development purposes only.

If you do not use Create React App, add this plugin to your Babel setup. Just remember to disable it before deploying your app to production.

You can return arrays from render()

In React 15.x, you had to place multiple components in a container tag (usually a div) before you can return them in render(), increasing the amount of needless containers in your code. This was because the render method could only return a single root node.

1class Component extends React.Component {
2      const todos = [{ title: "Build react app", id: 123723 } ...];
3      render() {
4        return (
5          <ul>
6            {todos.map((todo) => <List key={todo.id} title={todo.title} />)}
7          </ul>
8        );
9      }
10    }

React 16 improves this situation by allowing arrays to be returned from render so you can do away with needless wrapper tags that can only add more complexity to your code.

Now, you can return an array of components:

1class Component extends React.Component {
2      const todos = [{ title: "Build react app", id: 123723 } ...];
3      render() {
4        return todos.map((todo, index) => <List key={todo.id} title={todo.title} />);
5      }
6    }

Keep in mind that you need to provide a unique key prop when returning an array of elements, else you get a warning.

Browser compatibility

React 16 depends on the Map and Set collection types which are not supported natively in older browsers (anything before IE 11) and devices. If you support these older environments, consider adding a polyfill to your application to maintain compatibility. You can use core-js or babel-polyfill.

Here’s how a polyfilled environment for React 16 using core-js might look like:

1import 'core-js/es6/map';
2    import 'core-js/es6/set';
3
4    import React from 'react';
5    import ReactDOM from 'react-dom';
6
7    ReactDOM.render(
8      <h1>Hello, world!</h1>,
9      document.getElementById('root')
10    );

Furthermore, it also depends on requestAnimationFrame, even in test environments. Here’s a simple shim for test purposes:

1if (!window.requestAnimationFrame) {
2      window.requestAnimationFrame = function(callback) {
3        setTimeout(callback, 0);
4      };
5    }

Wrap Up

These are some of the most interesting features coming in React 16 that you should take note of. The first beta is already available for testing and, since all unit tests pass, it should be safe enough to use right away.

To install the beta use:

1yarn add react@next react-dom@next
2    // or 
3    npm install --save react@next react-dom@next