React: Calling ReactDOM.render() many many times is slow

Created on 26 Apr 2018  Â·  8Comments  Â·  Source: facebook/react

Do you want to request a feature or report a bug?

Bug (?)

What is the current behavior?

Calling ReactDOM.render() many many times seems to have meaningfully worse performance than rendering many elements within a single React root.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:

// Edited to add dev/production builds

(These examples are obviously pretty contrived)

What is the expected behavior?

In a perfect world there wouldn't be such a large performance discrepancy between these two approaches.

For context, I'm working with a frontend plugin framework and trying not to expose React (which should ideally be an implementation detail) as part of the plugin interface.

An interface like this requires both the host and the plugin to be implemented with React and to share the same instance of React... but is fast and convenient when they do:

render(props: T): JSX.Element;

An interface like this treats React as an implementation detail, but is less convenient and (more importantly) incurs the above performance problem:

render(props:T, element: HTMLElement): void; 
unmount(element: HTMLElement): void;

I imagine this use case isn't a high priority for React/ReactDOM, but I'd love to understand a bit better what it is that really causes the performance difference and whether it's likely to ever change.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

  • Seems to impact both React 15 & 16 similarly
  • I don't think it's browser dependent
Needs Investigation

Most helpful comment

I fixed the biggest difference that jumped out at me in https://github.com/facebook/react/pull/13335.

There's some other differences in DEV (e.g. in React 15 we don't emit perf measurements by default, but in React 16 we do, and they have a fixed cost). But in production it should get better in the next patch release that includes https://github.com/facebook/react/pull/13335 (presumably 16.4.3 or 16.5.0).

Note in general though we don't recommend using many roots when it's avoidable. It's supported of course but it makes React's job more difficult. As far as I can see, if you use createPortal() in the render method instead of having many ReactDOM.render() calls, the performance significantly improves even in the version of React that's currently published. In general, I think your goal of abstracting away React can make it more difficult for you to benefit from React-specific optimizations like time slicing in the future — so that's another thing to consider.

Thanks for the repro case!

All 8 comments

I haven't looked yet but my first question is whether the difference is significant in production mode. The fiddles you're showing are running in development mode.

From what I've seen the difference is similar in production mode, you just start to really see it at higher element counts.

I've played around a little trying to understand how different usage patters impact it. Frequency of updates which require a DOM change, for example, or updates vs lots of mounting & unmounting components. So far I haven't really isolated anything particularly useful.

Calling ReactDOM.render() many many times seems to have meaningfully worse performance than rendering many elements within a single React root.

Not sure if I understand React under the hood correctly, but let's think about it.
When you call ReactDOM.render you need to destroy/replace contents of the target node.
Meanwhile, if you use a single root then Virtual DOM will take care of DOM operations.

We know that DOM mutations are pretty (very?) expensive.

So.. I wouldn't be surprised.

When you call ReactDOM.render you need to destroy/replace contents of the target node.

Is this actually true? My understanding is that that's actually not the case. With the repro examples what's actually being rendered is the same in either case way so the number of DOM mutations don't have to be wildly different.

That said, I can understand some overhead for each root and how many roots might make batching / scheduling worse -- I just don't know enough about the guts of React's implementation to fully explain it.

There shouldn't be a difference in DOM mutations.

I fixed the biggest difference that jumped out at me in https://github.com/facebook/react/pull/13335.

There's some other differences in DEV (e.g. in React 15 we don't emit perf measurements by default, but in React 16 we do, and they have a fixed cost). But in production it should get better in the next patch release that includes https://github.com/facebook/react/pull/13335 (presumably 16.4.3 or 16.5.0).

Note in general though we don't recommend using many roots when it's avoidable. It's supported of course but it makes React's job more difficult. As far as I can see, if you use createPortal() in the render method instead of having many ReactDOM.render() calls, the performance significantly improves even in the version of React that's currently published. In general, I think your goal of abstracting away React can make it more difficult for you to benefit from React-specific optimizations like time slicing in the future — so that's another thing to consider.

Thanks for the repro case!

@gaearon

if you use createPortal() in the render method instead of having many ReactDOM.render() calls

If so, why in the React tests we use calling the ReactNoop.render or ReactDOM.render many times, should we use other way to test such as setState in the root component?

The difference is not significant enough to matter in tests.

Was this page helpful?
0 / 5 - 0 ratings