Concurrent Mode

The findings from the Human-Computer Interaction research into real UIs.

About Speaker

Muhammad Omer Khan

Software Engineer @ KoderLabs

Open Source

Concurrent Mode

(Experimental)

  1. Introduction to Concurrent Mode.
  2. Suspense for Data Fetching.
  3. Concurrent UI Patterns.
  4. Adopting Concurrent Mode

Introduction To Concurrent Mode

Blocking Rendering

Once our code starts rendering an update, including creating new DOM nodes and running the code inside components, they can’t interrupt this work. We’ll call this approach “blocking rendering”.

Interruptible Rendering

In Concurrent Mode, rendering is not blocking. It is interruptible. This improves the user experience. It also unlocks new features that weren’t possible before.

Intentional Loading Sqeuences

In Concurrent Mode, we can tell React to keep showing the old screen, fully interactive, with an inline loading indicator. React starts preparing the new screen in memory first, And when the new screen is ready, React can take us to it.

Concurrency

  • For CUP-bound updates (such as 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 (such as 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 laoding states.

In Concurrent Mode, React can work on several state updates concurrently — just like branches let different team members work independently.

Suspense for Data Fetching

const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded

// Show a spinner while the profile is loading
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>

Suspense With Lazy Loading

Suspense for Data Fetching is a new feature that lets you also use <Suspense> to declaratively “wait” for anything else, including data.

Common Misconceptions

What Suspense is not?

1. It is not a data fetching implementation

2. It is not a ready-to-use client.

3. It does not couple data fetching to the view layer.

What Suspense Lets you Do

1. It lets data fetching libraries deeply integrate with React.

2. It lets you orchestrate intentionally designed loading states.

3. It helps you avoid race conditions.

Traditional Approaches vs Suspense

Before

Fetch-on-render:

Start rendering components. Each of these components may trigger data fetching in their effects and lifecycle methods. This approch often leads to "waterfalls".

(For Example, fetch in useEffect | componentDidMount)

Render-as-you-fetch:

Start fetching all the required data for the next screen as early as possible, and start rendering the new screen immediately.

Now

Suspense and Race Conditions

Race conditions are bugs that happen due to incorrect assumptions about the order in which our code may run. Fetching data in the useEffect Hook or in call lifecycle methods like componentDidUpdate often leads to them. Suspense can help here, too

Handling Errors

// Error boundaries currently have to be classes.
class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null };
  
  static getDerivedStateFromError(error) {
    return {
      hasError: true,
      error
    };
  }
  
  render() {
    if (this.state.hasError) {
      return this.props.fallback;
    }
    return this.props.children;
  }
  
}
function ProfilePage() {
  return (
    <Suspense fallback={<h1>Loading profile...</h1>}>
      <ProfileDetails />
      <ErrorBoundary fallback={<h2>Could not fetch posts.</h2>}>
        <Suspense fallback={<h1>Loading posts...</h1>}>
          <ProfileTimeline />
        </Suspense>
      </ErrorBoundary>
    </Suspense>
  );
}

Using ErrorBoundary

Concurrent UI Patterns

Transitions

function App() {
  const [resource, setResource] = useState(initialResource);
  const [startTransition, isPending] = useTransition({
    timeoutMs: 3000
  });
  // ...
<button
  onClick={() => {
    startTransition(() => {
      const nextUserId = getNextId(resource.userId);
      setResource(fetchProfileData(nextUserId));
    });
  }}
>

SuspenseList

function ProfilePage({ resource }) {
  return (
    <SuspenseList revealOrder="forwards">
      <ProfileDetails resource={resource} />
      <Suspense fallback={<h2>Loading posts...</h2>}>
        <ProfileTimeline resource={resource} />
      </Suspense>
      <Suspense fallback={<h2>Loading fun facts...</h2>}>
        <ProfileTrivia resource={resource} />
      </Suspense>
    </SuspenseList>
  );
}

Adopting Concurrent Mode

Installation

npm install react@experimental react-dom@experimental

Index.js

import ReactDOM from 'react-dom';

// If you previously had:
//
// ReactDOM.render(<App />, document.getElementById('root'));
//
// You can opt into Concurrent Mode by writing:

ReactDOM.createRoot(
  document.getElementById('root')
).render(<App />);

Questions?

Thankyou

Resources:

Slides Link

Code Link

deck

By Omer Khan

deck

  • 282