Skip to content

Instantly share code, notes, and snippets.

@jakwuh
Last active March 21, 2019 11:06
Show Gist options
  • Save jakwuh/3cc50d28561d442ce34206ef0be59193 to your computer and use it in GitHub Desktop.
Save jakwuh/3cc50d28561d442ce34206ef0be59193 to your computer and use it in GitHub Desktop.
React 16 and beyond

React 16 and beyond

This document is created to overview existing React 16+ features. Last updated: 21/03/2019

New fragments syntax

Earlier React supported only single return from the render method:

    render() {
        return (
            <div>
                <Tab id={1} />
                <Tab id={2} />
                <Tab id={2} />
            </div>
        );
    }

react@?? introduced React.Fragment component which allows to render an array of elements without any extra wrapper element:

    render() {
        return (
            <React.Fragment>
                <Tab id={1} />
                <Tab id={2} />
                <Tab id={2} />
            </React.Fragment>
        );
    }

react@16.0.0 allows render method to return an array of elements:

    render() {
        return [
            <Tab key={1} id={1} />,
            <Tab key={2} id={2} />,
            <Tab key={3} id={2} />,
        ];
    }

However, returning an array from render method has a number of downsides:

  • Children in an array must be separated by commas
  • Children in an array must have a key
  • Strings must be wrapped in quotes

So far, react@16.2.0 introduced special fragment syntax:

    render() {
        return (
            <>
                <Tab id={1} />
                <Tab id={2} />
                <Tab id={2} />
            </>
        );
    }

Though this syntax isn't supported very well:

  • babel@7.0.0-beta.31
  • eslint@3.x + babel-eslint@7
  • Webstorm@2018.1.1 still doesn't support the syntax (VS code does)

Also, if you need to pass key property to the fragment you still have to use React.Fragment as soon as <></> syntax does not support passing props.

    render() {
        return this.definitions.map(item => (
            <Fragment key={item.id}>
                <dt>{item.term}</dt>
                <dd>{item.description}</dd>
            </Fragment>
        ));
    }

Extended support for return types

See the code below:

    return 'now i can return strings'; // react@16.0.0
    return undefined; // react@16 (?) - will not convert to 'undefined'
    return true;
    return false; // react@16 (?) - will not convert to 'false' / 'true'

Error boundaries

react@16.0.0 introduces error boundaries. An error boundary is a component which defines componentDidCatch method (legacy name is unstable_handleError).

class ErrorBoundary {
    state = {
        hasError: false,
    };

    componentDidCatch(error, info) {
        this.setState({ hasError: true });
        logErrorToKibana(error, info);
    }

    render {
        // ...
    }
}

Usage:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

info contains error metadata, e.g. info.componentStack.

Also, as of react@16.0.0 if you do not define an error boundary the whole tree gets unmounted on exception in lifecycle methods.

Portals

class Tooltip {
    render() {
        return ReactDOM.createPortal(
            <TooltipView
                text={this.props.text} 
                position={this.props.position}
            />,
            document.querySelector("#tooltips-container),
        );
    }
}

Event bubbling works according to vDOM, not real DOM.

Improved server-side rendering

react@16.0.0 improved support for SSR.

1:1 support for render return types

SSR methods such as renderToString or renderToStaticMarkup support same return types as React's render method:

    renderToString(<MyApp />)
    renderToString([<Head /><Body />])
    renderToString("render any text")
    renderToString(42)
More efficient html

In react@15 the typical ssr-rendered html looks like this:

<div data-reactroot="" data-reactid="1" 
    data-react-checksum="122239856">
  <!-- react-text: 2 -->This is some <!-- /react-text -->
  <span data-reactid="3">server-generated</span>
  <!-- react-text: 4--> <!-- /react-text -->
  <span data-reactid="5">HTML.</span>
</div>

The typical html for react@16.0.0 is:

<div data-reactroot="">
  This is some <span>server-generated</span> <span>HTML.</span>
</div>
renderhydrate

On the client-side you have to explicitly call hydrate instead of render to render React app using server-side html. Also, hydration client checks become less strict. One of the most important changes:

<...> when the client-side renderer detects a markup mismatch, it only attempts to change the HTML subtree that doesn’t match, rather than the entire HTML tree.

Source

Smaller precompiled bundles

React bundle is precompiled for both development and production environments:

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./cjs/react.production.min.js');
} else {
  module.exports = require('./cjs/react.development.js');
}
Faster renders

SSR doesn't require vDOM, as soon as it is thrown away right after rendering the html content. Omitting vDOM creation increased the render speed.

suppressHydrationWarning

react@16.1.0 introduces suppressHydrationWarning prop. It is used to suppress warnings when a content differs between client-side and server-side, e.g. timestamps.

<div suppressHydrationWarning>{Date.now()}</div>
Streaming support

2 new methods were introduced: renderToNodeStream and renderToStaticNodeStream. They return Readable which can be easily piped to the response. This improves TTFB.

    const stream = renderToNodeStream(<App />);

    stream.pipe(
        res, // express response object
        { end: false },
    );
Vulnerability patches

Starting from react@16.0.0 there is a known vulnerability when using React SSR. react@16.4.2 fixes the issue. Also patches has been published to each minor react version containing only security fixes.

See the example of vulnerability below:

let props = {};
props[userProvidedData] = "hello";
let element = <div {...props} />;
let html = ReactDOMServer.renderToString(element);
let userProvidedData = '></div><script>alert("hi")</script>';
<div ></div><script>alert("hi")</script>
A note on SSR

SSR, especially with streams is not that easy. Even with react@16.0.0 update you have to remember a lot of things, e.g.:

  • Global objects are forbidden
  • An error can happen in a stream. Status will be 200, though a html content will be corrupted
  • Error boundaries are not supported by SSR
  • React portals are not supported by SSR
  • Lots of APIs differs: e.g. fetch

Support for custom DOM attributes

In react@16.0.0 you can pass any attribute to the html native element. It will be passed through:

<div data-test-id="filter"><Select /></div>
<div data-test-id="filter"><!-- table html --></div>
<!--  Be aware: everything will be stringified: -->
<div data-test-id="[object object]"><!-- table html --></div>

New lifecycles

Deprecated lifecycles:

componentWillMount => UNSAFE_componentWillMount
componentWillReceiveProps => UNSAFE_componentWillReceiveProps
componentWillUpdate => UNSAFE_componentWillUpdate

New lifecycles:

static getDerivedStateFromProps(props, state): null | stateToMergeBeforeRender

and

getSnapshotBeforeUpdate(prevProps, prevState): snapshot
componentDidUpdate(prevProps, prevState, snapshot): void

Lifecycles diagram: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

getDerivedStateFromProps: memoize computable properties
static getDerivedStateFromProps(props, state) {
    if (
        props.list !== state.prevPropsList ||
        state.prevFilterText !== state.filterText
    ) {
        return {
            prevPropsList: props.list,
            prevFilterText: state.filterText,
            filteredList: props.list.filter(
                item => item.text.includes(state.filterText)
            )
        };
    }
    return null;
}

The code above could be replaced with memoization:

import memoize from "memoize-one";
// ...
    filter = memoize(
        (list, filterText) => list.filter(item => item.text.includes(filterText))
    );

    render() {
        const filteredList = this.filter(this.props.list, this.state.filterText);
        // ...
    }
}
getSnapshotBeforeUpdate: save scroll position
getSnapshotBeforeUpdate(prevProps, prevState) {
    return this.listRef.scrollHeight;
}

componentDidUpdate(prevProps, prevState, scrollHeight) {
    this.listRef.scrollHeight = scrollHeight;
}

To help migration to new lifecycles there is a rename-unsafe-lifecycles codemod available.

New context API

react@16.3.0 introduces new (really exciting!) context API. See the usage example below:

// withFetch.jsx
import React from 'react';
import axios from 'axios';

const {
    Provider: FetchProvider,
    Consumer: FetchConsumer
} = React.createContext(axios);

export {
    FetchProvider,
    FetchConsumer
};
// withFetch.spec.jsx
describe('FetchConsumer / FetchProvider', () => {
    it('provides axios fetch by default', (done) => {
        render(
            <FetchConsumer>
                {(fetch) => {
                    expect(fetch).to.equal(axios);
                    done();
                }}
            </FetchConsumer>
        );
    });

    it('allows to use custom fetch', (done) => {
        const fetch = () => {
        };

        render(
            <FetchProvider value={fetch}>
                <FetchConsumer>
                    {(_fetch) => {
                        expect(_fetch).to.equal(fetch);
                        done();
                    }}
                </FetchConsumer>
            </FetchProvider>
        );
    });
});

New ref API

As of react@16.3.0 there are two new methods available: React.createRef and React.forwardRef. See the usage example below:

class Example {
    constructor() {
        this.inputRef = React.createRef();
    }
    
    componentDidMount() {
        this.inputRef.focus();
    }
    
    render() {
        return <input ref={this.inputRef} />;
    }
}
const Input = React.forwardRef((props, ref) => (
    <input ref={ref} />
);

// ...

<Input ref={this.input} />

<StrictMode>

react@16.3.0 adds StrictMode component. StrictMode to React is same as 'use strict' to Javascript. It ensures following things:

  • No usage of UNSAFE_ lifecycles
  • No usage of deprecated APIs such as string ref
  • Determinism of some lifecycles: constructor, render, getDerivedStateFromProps

StrictMode affects only development mode.

PointerEvents

react@16.4.0 adds support for pointer events e.g. onPointerDown, onPointerMove etc.

Fiber

See upcoming features to understand usage.

Smaller size

react + react-dom is 109 kb (34.8 kb gzipped), down from 161.7 kb (49.8 kb gzipped).

New Profiler

react@16.5.0 adds new "Profiler" tab to React DevTools. Features:

  • Flame chart + changed props
  • Ranked chart
  • Component chart

React.memo

react@16.6.0 introduces React.memo HOC which is exactly the same as pure HOC from recompose library.

export default React.memo(props => (
    <button onClick={props.onClick}>{props.title}</button>
));

React.lazy & React.Suspense

react@16.6.0 introduces a way to dynamically load chunks. Example: https://codesandbox.io/s/jpr4o8r7ky?fontsize=14

const LazyComponent = React.lazy(() => import('./LazyComponent'));

const App = () => (
    <React.Suspense fallback={<Loader />}>
        <LazyComponent foo="bar" />
    </React.Suspense>
)

static contextType

react@16.6.0 introduces a convenient way to consume value from (only single) context within a class:

const MyContext = React.createContext();

class MyComponent extends React.Component {
    static contextType = MyContext;
    
    render() {
        /* this.context contains context value */
    }
}

getDerivedStateFromError

react@16.6.0 introduces a new method to update the state in response to an error in react tree.

class ErrorBoundary extends React.Component {
    state = { hasError: false };

    static getDerivedStateFromError => () => ({ hasError: true })

    render() {
        if (this.state.hasError) {
          return <h1>Something went wrong.</h1>;
        }

        return this.props.children; 
    }
}

Deprecations to <StrictMode>

react@16.6.0 deprecated two more APIs when using StrictMode:

  • ReactDOM.findDOMNode()
  • Legacy Context

Hooks

react@16.8.0 introduces hooks.

  • useState
  • useEffect
    • simple effect
    • effect with unsubscribe (+ explanation of a common bug)
    • single run effect ([])
    • dependent effetct ([count])
  • useContext

Example: https://codesandbox.io/s/z23vv8lro4?fontsize=14 Recompose: https://github.com/acdlite/recompose Why recommend Hooks API: acdlite/recompose#756

Upcoming features:

Async rendering

Ever wonder what "async rendering" means? Here's a demo of how to coordinate an async React tree with non-React work https://t.co/3snoahB3uV pic.twitter.com/egQ988gBjR

— Andrew Clark (@acdlite) 18 сентября 2017 г.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment