React 16.6.0 Goodies

Avatar of Kingsley Silas
Kingsley Silas on

React 16.6.0 was released October 2018 and with it came goodies that spice up the way we can develop with React. We’re going to cover what I consider the best of those new goodies with examples of how we can put them to use in our work.

React.memo() avoids unnecessary re-rendering

There are situations where a component re-renders, even if neither its state nor its props changed. That adds up and can be an expensive operation.

Here’s an example of a counter to show what we’re talking about:

See the Pen
React counter w/o React.memo()
by CSS-Tricks (@css-tricks)
on CodePen.

We have a child component that receives a specific value as props that do not change.

const Child = props => {
  console.log("rendered");
  return <React.Fragment>{props.name}</React.Fragment>;
}

The child’s value is determined by the state of the App component. It’s state doesn’t change. It’s props remain the same.

class App extends React.Component {
  state = {
    count: 1,
    name: "Jioke"
  };

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    });
  };

  render() {
    return (
      <React.Fragment>
        <Child name={this.state.name} />
        <div>{this.state.count}</div>
        <button onClick={this.handleClick}>+</button>
      </React.Fragment>
    );
  }
}

Yet, each button click results in two things happening: the value of count is incremented and the child component is re-rendered. Just watch:

We could resolve this with a class component using the shouldComponentUpdate() lifecycle hook, which would look like this:

class Child extends React.Component {
  
  // No re-render, please!
  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.name != this.props.name
  }
  
  render() {
    console.log('rendered')
    return <React.Fragment>{this.props.name}</React.Fragment>
  }
}

That’s where React.memo() comes into play. It’s a higher-order component we can wrap around the child and, presto, now the child is shielded from unnecessary additional rendering.

const Child = React.memo(props => {
  console.log("rendered");
  return <React.Fragment>{props.name}</React.Fragment>;
});

See the Pen
React.memo 2
by CSS-Tricks (@css-tricks)
on CodePen.

React.lazy() makes importing files a breeze while Suspense provides a fallback UI

Code splitting is crucial in web development—it enables us to import only the files we, which is not only reduces an application’s initial load, but is a core principle of the React framework.

Well, React now enables code splitting using React.lazy() and suspense right at the component level.

By default, if making use of a component (even if its usage depends on a condition), then we import it into the file where you will be using it. React.lazy() can now handle the importation like this:

const MyCounter = lazy(() => import("./Counter"));

This single line returns a promise that resolves to the imported component. From here, we can use the component as we normally would.

const App = () => (
  <div>
    <MyCounter />
  </div>
);

There are cases where we might want to render a fallback UI before the component is ready to render. For example, it might take a moment for an API call to fetch and return data. This is a great opportunity to show a loading state while the user waits. Suspense can do just that.

// Using React.lazy() to import the Counter component
const MyCounter = lazy(() => import("./Counter"));
const App = () => (
  <div>
    // Using Suspense to render a loading state while we wait for the Counter
    <Suspense fallback={<div>Loading...</div>}>
      <MyCounter />
    </Suspense>
  </div>
);

Suspense’s fallback prop can accept a React element, so go nuts. It can be used to display whatever fallback UI we want while the component loads.

contextType accesses provider context and passes state without render props

The Context API made it possible to share state among multiple components without having to make use of a third-party library.

Well, React 16.6 makes it possible to declare contextType in a component to access the context from a provider. This saves us from having to make use of render props to pass down context to the consumer.

See the Pen
React contextType
by CSS-Tricks (@css-tricks)
on CodePen.

First, let’s create our context:

const UserContext = React.createContext({});

const UserProvider = UserContext.Provider;
const UserConsumer = UserContext.Consumer;

We’ll make use of the provider in the App component:

class App extends React.Component {
  state = {
    input: "",
    name: 'John Doe'
  };

  handleInputChange = event => {
    event.preventDefault();
    this.setState({ input: event.target.value });
  };

  handleSubmit = event => {
    event.preventDefault();
    this.setState({ name: this.state.input, input: '' })
  };
  render() {
    return (
      <div>
        <UserProvider
          value={{
            state: this.state,
            actions: {
              handleSubmit: this.handleSubmit,
              handleInputChange: this.handleInputChange
            }
          }}
        >
          <User />
        </UserProvider>
      </div>
    );
  }
}

The provider passes the state and the methods to consumer components that will make use of them via the value prop. To access the context, we’ll make use of this.context instead of making render props like we normally would.

class User extends React.Component {
  static contextType = UserContext;
  render() {
    const { state, actions } = this.context;
    return (
      <div>
        <div>
          <h2>Hello, {state.name}!</h2>
        </div>
        <div>
          <div>
            <input
              type="text"
              value={state.input}
              placeholder="Name"
              onChange={actions.handleInputChange}
            />
          </div>
          <div>
            <button onClick={actions.handleSubmit}>Submit</button>
          </div>
        </div>
      </div>
    );
  }
}

We set static contextType to UserContext which we created earlier. With that, we are able to extract the context which includes the state and methods from this.context. We make use of ES6 destructuring to get the values so we can make use of them in the User component, which is the consumer. This looks so much cleaner and is easier to read compared to doing this with render props.

getDerivedStateFromErrors()

We have error boundary to handle errors, which makes use of componentDidCatch() and that gets fired after the DOM has been updated. It’s well suited for error reporting. But now we have getDerivedStateFromErrors() to render a fallback UI before the render completes if an error is caught. Sort of the same concept as Suspense, but for error states instead of loading states.

See the Pen
React getDerivedStateFromError
by CSS-Tricks (@css-tricks)
on CodePen.

Let’s create our error boundary component to capture the moment something goes awry:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false
    };
  }
  
  // If hasError is true, then trigger the fallback UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  // The fallback UI
  render() {
    if (this.state.hasError) {
      return (
        <h1>Oops, something went wrong :(</h1>
      );
    }
    return this.props.children;
  }
}

We make use of getDerivedStateFromError() to spot that an error was caught by the error boundary and then return hasError as true when an error occurs. When this happens, we want to display a message to inform the user that an error has encountered.

class Counter extends React.Component {
  state = {
    count: 1
  }

  handleClick = () => {
    this.setState({
      count: this.state.count + 1
    })
  }

  // If the count is greater than 5, throw an error
  render() {
    if (this.state.count > 5) {
      throw new Error('Error')
    }
    return (
      <div>
        <h2>{this.state.count}</h2>
        <button onClick={this.handleClick}>+</button>
      </div>
    )
  }
}

That’s going to trigger an error when the value of count is greater than five. Next, we need to wrap our Counter component as a child of ErrorBoundary component to apply the error conditions to the component:

const App = () => (
  <div>
    // Wrap the component in the ErrorBoundary to attach the error conditions and UI
    <ErrorBoundary>
      <Counter />
    </ErrorBoundary>
  </div>
)

We can even limit the error to the specific piece that is broken. So, for example, let’s take a listing of locations. Instead swapping the entire list of locations for the error UI, we can slap it at the specific location where the error happened.

See the Pen
React getDerivedStateFromError 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Pretty nice, right?

React continues to add a bunch of useful features while making it easier to write code with each release and v16.6 is no exception. If you’ve already started using any of the latest goodies that shipped in this release, please let me—I’d be interested in seeing how you’re using them in a real project.

More Information