React Batch Mount
Tags: react, preformance
Published: 2021-03-02 05:47:42

Hey there!
In the Wild Internet there are many different articles about increase performance of React applications. But mainly these articles describe how to reduce the number of component rerenders. What if the application takes a long time to mount components? 

Let's describe the problem. Your user wants to see a list of some items (for example, musics, messages, transactions, etc). This list may contain thousands of items, and each item is mounted complex component with calculations effects and even explicit manipulation of the children. Obviously, mounting all components for all items at once is a very bad idea and no on does that.

What is usually done in this case? In this case, a tichnique is used that allows you not render components for all data. These are techniques such as pagination, lazy loading, virtualization and so on. But what if the list of items in the user interface is represented by a form with thousands of inputs? In some cases, you can any of the previous techniques, but in other cases you must render all components for correct form work and a good UX.

One solution to this problem is not to mount all the components at once, but in small parts. In this case, the user will have to wait until all the components are mounted, but the browser will have time to print frames and the user will see the dynamic loading and even interact with loaded part.

## `React.Suspense` and `React.lazy`
How to implement this idea? The straightforward way is use the component that has some state and provides the knowledge which children components are mounted. This can lead to problems with extra renders, complex memoization and so on.

From [React 16.6](https://reactjs.org/blog/2018/10/23/react-v-16-6.html), you can use [`React.Suspense`](https://reactjs.org/docs/react-api.html#reactsuspense) and [`React.lazy`](https://reactjs.org/docs/react-api.html#reactlazy) for rendering components in the dynamic loaded modules. `lazy` returns a special component that is specially mounted and processed in the React tree. The dynamic import returns a promise that is wrapped in the Lazy component. When the promise is fulfilled, the Lazy component is **pointwise** updated without triggering an update to its ancestors. We can replace the dynamic import promise to a promise that we control and mount content of Lazy component when we want.
```jsx
// The simple react component
const Some = () => <div>It's lazy wrapped component</div>;

// Create a promise that is resolved by a object that
// looks like a dynamic import object
const promise = Promise.resolve({default: Some});

// Wrap the promise the lazy function
const LazyWrapped = lazy(() => promise);
```
Now we can try to mount `LazyWrapped` component to React tree and get an error
```
A React component suspended while rendering, but no fallback UI was specified.
```
The Lazy component requires React.Suspense to be among its ancestors. These components is completely controlled by React.
```jsx
const App = () => (
  <Suspense fallback="Loading ...">
    {Array.from({ length: 100 }).map((_, i) => (
      <LazyWrapped key={i} />
    ))}
  </Suspense>
);
```
[Demo](https://codesandbox.io/s/react-batch-mount-1-6974b)

What about the rerenders of these components? Let's add `console.log` to several components.
```jsx
// HOC create component that close n
const Some = (n) => () => {
  console.log("render Some", n);
  return <div>It's lazy wrapped component</div>;
};

const LazyWrapped1 = lazy(
  () =>
    new Promise((resolve) => {
      setTimeout(() => {
        console.log("promise 1 resolved");
        resolve({ default: Some(1) });
      }, 300);
    })
);

const LazyWrapped2 = lazy(
  () =>
    new Promise((resolve) => {
      setTimeout(() => {
        console.log("promise 2 resolved");
        resolve({ default: Some(2) });
      }, 500);
    })
);

const App = () => {
  console.log("render App");
  return (
    <Suspense fallback="Loading ...">
      <LazyWrapped1 />
      <LazyWrapped2 />
    </Suspense>
  );
};
```
Now we have only two Lazy component, but theirs promises are fulfilled at different times. When the root component and the lazy components are rerender, they print to the console about it. And the promises print too when its are resolved. Let's take a look at the console.
```
render App 
promise 1 resolved 
render Some 1
promise 2 resolved 
render Some 2
```
[Demo](https://codesandbox.io/s/react-batch-mount-2-xtce6)

How you can see when the promises are fulfilled only the Lazy component rerenders. Therefore we can create some mechanism that controls promises fulfilled. It'll allow to control mount of the components without rerendering other parts of the application.

## react-batch-mount
I try to implement its mechanism in [react-batch-mount](https://www.npmjs.com/package/react-batch-mount) library.
The main part is hidded inside the library and name `scheduler`. The scheduler has queue of the promises resolve functions. If the queue isn't empty the scheduler plans the next batch mount via `requestAnimationFrame`.

To connect a component to batch rendering, you can use the HOC `batchMount`.
```jsx
const Some = batchMount(() => {
  return <div>It's batched component</div>;
});
```
`batchMount` internally creates a promise which will be resolved by the scheduler. This promise is wrapped in `React.lazy` and the Lazy component is returns by `batchMount`. We can use `Some` component inside `Suspense` in our App.
```jsx
const App = () => {
  return (
    <div>
      <Suspense fallback={<div>Loading ... </div>}>
        {Array.from({ length: 50 }).map((_, i) => (
          <Some key={i} />
        ))}
      </Suspense>
    </div>
  );
};
```
[Demo in TypeScript](https://codesandbox.io/s/awesome-wildflower-gooql?file=/src/index.tsx)

You can pass options to `batchMount` at the second parameter. One of the options if `fallback`. If `fallback` is passed `batchMount` wrap the Lazy component to `Suspense` with the passed `fallback`. This will create `Suspense` to each item.
```jsx
const Some = batchMount(
  () => {
    return <div>It's batched component</div>;
  },
  {
    fallback: <div>Loading</div>
  }
);
```
[Demo](https://codesandbox.io/s/react-batch-mount-3-kcv7l)

By default the scheduler mounts component at a time. You can configure its behavior using the `setGlobalConfig` function. This function takes either `{batchSize: number}` or `{budget: number}`. If you explicitly specify the batch size, the scheduler will fill the batch of the specified size if there are enough components. `budget` is the time in milliseconds that the scheduler should try to spend on mounting the batch. When the previous batch is fully mounted, the scheduler will calculate the size of the next batch based on the mount time of the previous and specified budget.

To see the full power of the budget, let's try to simulate a long component mounting.
```tsx
const useHighload = (ms) => {
  const mounted = useRef(false);
  if (!mounted.current) {
    mounted.current = true;

    const start = Date.now();
    let now = Date.now();
    while (now - start < ms) {
      now = Date.now();
    }
  }
};

const Some = batchMount(
  () => {
    useHighload(100);
    return <div>It's batched component</div>;
  },
  {
    fallback: <div>Loading</div>
  }
);

setGlobalConfig({budget: 500});
```
[Demo](https://codesandbox.io/s/react-batch-mount-4-wh3rp)

## What's next

Now you know a new approach to optimizing a React application and several internal features of React. You can look at the rest of [react-batch-mount](https://www.npmjs.com/package/react-batch-mount) features, experiment with it, maybe even use it in your application.