React Concurrent Mode

Regardless of all the optimizations we do in in React apps, rendering process so far has been a blocking and cannot be interrupted.
With the introduction of new experimental React Concurrent Mode, rendering becomes interruptible.

Previous workarounds

Debounce and throttle are common workarounds to mitigate for example stutter in an input field while typing.

Debounce – Only update the list after the user stops typing
Throttle – Update the list with a certain maximum frequency

Similar to git branches

Concurrent Mode is like React working on git branches.

Eg:- Navigating between two screens in an app

In React Concurrent Mode, we can tell React to keep showing the old screen(like another git branch), fully interactive, with an inline loading indicator. And when the new screen is ready, React can take us to it.

Concurrency

CPU

For CPU-bound updates like creating DOM nodes and running component code, concurrency means that a more urgent update can “interrupt” rendering that has already started.

For IO-bound updates like fetching code or data from the network, concurrency means that React can start rendering in memory even before all the data arrives, and skip showing jarring empty loading states.

Install

Install the experimental version of react and react-dom.

Run your app, run your build, run your tests/type checking and make sure there are no issues.

Likely issues:

  1. String Refs, Legacy Context, or findDOMNode
  2. All the lifecycle methods that have the `unsafe_` prefix

Enable Concurrent Mode:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

const rootElement = document.getElementById('root');
// ReactDOM.render(<App />, rootEl)
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);

Try Concurrent Mode

Concurrent Mode enables two new features:

  1. Time slicing
  2. Suspense for everything asynchronous

Let’s create an app for testing out these features with create-react-app.

Experimental

The API for suspense is likely to change before suspense is officially released, so this is still in experimental stage.

Write a function for creating an actor when an actor name is provided in the input field:

function createActor(name) {
  let status = 'pending';
  let result;
  let suspender = getActor(name).then(
    name => {
      status = 'success';
      result = name;
    },
    error => {
      status = 'error';
      result = error;
    }
  );
  return {
    read() {
      if (status === 'pending') throw suspender;
      if (status === 'error') throw result;
      if (status === 'success') return result;
    }
  };

Let’s create a function to imitate an HTTP request:

function getActor(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`Your favorite actor is ${name}!`);
      // Uncomment below to test rejecting instead
      // reject(new Error(`Oops! could not load message for ${name}`))
    }, 1000);
  });
}

Now we can create a form so we can use the above functions

function Form() {
  const [actor, setActor] = useState(null);
  const [startTransition, isPending] = useTransition({
    timeoutMs: 3000
  });

  function handleSubmit(event) {
    event.preventDefault();
    const name = event.target.elements.nameInput.value;
    startTransition(() => {
      setActor(createActor(name));
    });
  }

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label htmlFor="nameInput">Name</label>&nbsp;
        <input id="nameInput" />
        &nbsp;
        <button type="submit">Submit</button>
      </form>
      <Suspense fallback={<p>Loading...</p>}>
        <Message actor={actor} isPending={isPending} />
      </Suspense>
    </div>
  );
}

function Message({ actor, isPending }) {
  return (
    <p style={{ opacity: isPending ? 0.4 : 1 }}>
      {actor ? actor.read() : 'Please provide an actor'}
    </p>
  );
}

Here is the full example of React concurrent mode in action:

Be first to comment

Leave a Reply