When does React re-render? Explaining rerenders and how to optimize performance
felixgerschau.com
felixgerschau.com
This is wrong. Naive React code is rarely fast in relative terms. This is because the whole design of React is not based on updating on the parts of the UI that have changed, but on re-rendering everything.
The whole concept of React, if you go back to the original "rethinking best practices" intro, was that Facebook wanted to bring the server-side practice of re-rendering the whole page for any change onto the client. This had the obvious advantage that, combined with normalised state flowing downward through the component tree, you would never end up with stale bits of UI, as everything would all always be kept up to date.
However, tearing down and re-creating the whole DOM for every single render was prohibitively expensive, so the virtual DOM was born. But, and this seems to get lost in a lot of discussions of React, VDOM wasn't some magical leap forward in UI rendering performance. It was a performance technique to make React's paradigm possible. Possible, but not inherently fast, because there is still a cost to re-rendering every component for every event. Any React developer who's had to add React.memo to half their components, and add visualisation to lists of just a few hundred items should know this.
Having written several substantial apps with both React and some of its competitor frameworks, I'm always amazed to see React-only developers continue to labor under the delusion than it is the fastest framework out there. It is not. Its paradigm is a beautiful one to develop with in most ways, but there is a significant price to pay in terms of performance.
It’s also important to note that the virtual DOM isn’t just (or perhaps even primarily) about performance. It’s also about maintaining DOM state across updates, like focus, cursors in text inputs, etc. Modern browsers on modern devices are fast enough that for many web apps you probably actually could just document.write the entire DOM from scratch on every component state update if you didn’t care about that DOM state.
What is it when the app is fast? It's probably React or Angular. Those are just the most popular frameworks, so when you look you'll probably see one of them. In good hands they can make fast apps. In poor hands they can make slow apps. Those frameworks aren't the problem.
I don't see how you could know that without reading the code.
Absolutely. As long as our app performs without any input lag on IE11 I'm happy. Our users don't care and our higher-ups don't care either. They care that we built out new features quickly and have a better user experience than our competitors. If React lets us trade some minor rendering performance for better developer productivity it's doing it's job.
Other than that I agree with the gist of your post
Maybe a side-effect of the poor equality we have in JS-land.
That internal tree update is the root cause of all the problems. During that update, React runs all the javascript in the component, and that of all of it's child components, which then run their child components till the entire tree is done.
Sure, only a small part of the actual DOM will change. But the sheer wastage that happens to update that small part is criminal.
Actually, stupidcar assessment is completely correct.
> React updates the entire tree internally when anything changes, but actually only changes DOM nodes who's prop or state has changed.
Sorry, but you're mixing up a lot of concepts here!
When you tell react(-dom) to render, it recursively calls all the render() methods of your entire component hierarchy and updates the entire virtual DOM, but only mutates the actual browser's DOM for the parts that are changed.
Yes, there are ways to block a render based on prop staleness using shouldComponentUpdate/PureComponent/React.memo. These are explicit optimisations on top of the default render-everything model.
Props and State play into this story quite a bit differently. When a component-local state changes react only renders that specific component (with everything below it). Props are passed down on every render and are by default not compared for staleness so don't prevent re-renders. Props and DOM updates have nothing to do with each other: props are properties of components, not the DOM! Sure, the DOM can be directly or indirectly be influenced by those props.
> Maybe a side-effect of the poor equality we have in JS-land.
Yes, this is true. A lot of reacts's quirks are a result of JS's poor notion of equality.
God, yes. And I'm amazed everyday to run into senior engineers that do not understand how equality works in JS.
People are also easily confused if they happen to use Redux, because Redux also adds a layer of memoization for you and will not re-render if none of the state (props) or dispatch functions change.
If you have a React Tree that outputs the HTML that is your full page some of your components in that tree will not be re-rendered because the virtual DOM tells you you do not need to re-render them (where we us re-render to mean the React render function is called for that particular part of the tree) but the last render of that component is still saved somewhere and when your whole tree is ready to re-render the HTML of your page (where re-render means actually sending changes to the DOM and causing all the page to re-render)
I don't know enough about how React internals work but I suppose if you have a parent that re-renders but a child does not (React render called again), both will still render (HTML render) at the time of updating the DOM, as I cannot see a logical way that it could not be so.
If a component renders in React, that is, the render function is called, then ALL children's render() function are also called, unless you do specific optimizations such as React.memo.
So the whole tree is rerendered.
When it comes time to apply those changes to the DOM, that is, generating HTML on the page, wherever your render() functions returned the same result as before, the DOM is not touched.
it may be that way with regular Components, but afaik Pure components are smarter - the idea of Pure components is that react only rerenders the components when the props change. (or something in a hook, if you're using those). though i guess a Pure component is basically equivalent to what you'd get with memo?
As you said yourself, only children components will be re-rendered. This is relative to the component that had it's state changed (using setState or the useState hook). So not the whole tree rerenders, only the affected component and it's children. And again, when we speak as "re-render", the render() functions of the components are called and then the VDOM diffing happens. That is, even if child components got -rerendered (in react terms), there might not be a DOM update at all (which is the slow, costly operation).
Crawling a large React tree's render stack is also a costly operation. Not as much as DOM manipulation but it is still costly, and React is created in such a way that it requires you to include everything in that stack, even if it never changes. Which is one reason we have Portals.
A comparison of mithril can see that React is almost 2 times as slow.
https://mithril.js.org/framework-comparison.html
The only reason to use React for SPA is the job market, and that companies like the fact that is backed by Facebook.
https://krausest.github.io/js-framework-benchmark/current.ht...
> Its paradigm is a beautiful one to develop with in most ways, but there is a significant price to pay in terms of performance.
I've worked with React on-and-off since around 2013. I can count on one hand the number of times that out-of-the-box idiomatic React code has incurred a significant price in terms of performance. The majority of my React apps just work, they just render fast enough by default. One time I've had to optimize it was when I had a React app that displayed a page of items from the database without any pagination: once it got to a few hundred items, it would indeed render very slowly. My optimization here was actually not to start memoizing or implement componentShouldUpdate -- it was to implement pagination and search in the app, because the UX was more important than rendering speed, and solving the UX solved the render speed.
I don't understand what kind of apps people are building with React that incur these significant rendering performance costs? I could understand it if you're building a realtime cryptocurrency exchange dashboard but I don't think most people are doing that?
We deal with large hospitals and airports, so it's normal for us to have tens of thousands of components mounted at once - pagination isn't a viable solution for our purposes.
We've had to tap basically every optimization that React apps can do when managing large numbers of elements - however this has been worth it for us:
- We can use the same code to render interactive plans on the client, and to render to print-quality PDFs on the server - We can use the same framework in our UI and plan rendering
The downside is that it's more CPU intensive than alternatives like a WebGL renderer, but for now that's a tradeoff worth making.
Mixing traditional React with WebGL is another option. There's react-three-fiber[1], and custom/simpler hybrids are also possible. Though I've not tried server-side rendering.
Sure, but it's uncommon that I want more than 100 items on a screen in a webapp. Infinite scroll is not an antipattern, but it is something that people should be a little bit cautious of.
Users like context and clear anchors -- I like to be able to hit a back/forward button and know where I'll end up. I also like pages to load quickly and not make a ton of background requests on mobile connections, so if I can have an initial page load under a few megabytes at max, and can wait for the user to request data before loading it -- that's sometimes an attractive proposition.
Multiple search engines used to experiment with infinite scrolling and virtual rows, and as far as I know, most of the big ones went back to pagination, and I think it's because sometimes pagination is genuinely better UX, particularly when you're scrolling through a long list of blog posts or search results. Of course you can put thousands of elements in the DOM, and you can implement virtual scrolling if you need tens of thousands or millions. But the DOM tree is ultimately meant to be a human-traversable interface, and a <ul> element with several thousand <li>s beneath it is arguably not very traversable.
There are no hard rules here and there are a lot of caveats to what I'm about to say -- but I now bias towards avoiding DOM trees that are so big that I couldn't stick the current tree in a text editor and mostly consume its content textually, and that includes on dynamic web apps. The current state I'm displaying to the user should be small enough that the user can wrap their head around it.
The fact that React is so slow that you basically need to at least occasionally think about how you're rendering state is a separate conversation.
Having ~100-200 items, I'd damn love to have them all on the screen. It seems like the best option out of the trio of render all, pagination or infinite scroll. Pagination is faster than infinite scroll, infinite scroll is more convenient than pagination, but both make it hard to reliably display data in sequence (as the whole data set isn't loaded immediately anymore) - and more importantly, both break search. However bad and bare-bones searching is in the browser, it's still better than search reimplementations in web apps, because the latter aren't any more sophisticated, and also have limited scope.
That's just my opinion, and a point of annoyance with modern SPAs. Most of the time when I have to scroll a lot, or traverse a bunch of pages, all I really want is to be able to CTRL+F for the thing I'm looking for without changing the state of the application.
I am not talking about infinite data here, but data that fits in one single request, that also fits in RAM but does not fit all at once as rendered DOM elements, so the fix is that the GUI widget for this table is smart and instead of rendering 1000 tables will not do that, will render just the visible part and the a few rows above and under the viewport, when you scroll the data in the table cels is changed and no new DOM elements are created. This should be soemthing that the developer should not think about, in this sane GUI toolkits you just do something like
widget.dataProvider =myData;
Edit:
Also for the case of youtube videos, you don't load "download" the thumbnails, those are just string, a sane toolkit will not load all those thumbnails until you bring them into view. I hate YT implementation, you get 3 rows of video then you scroll wait 2 seconds for next 3 rows to load, scroll again, wait again 2 seconds. You could have loaded the strings for all the videos, then lazy load the thumbnails when needed and skip the step of fetch me the next page of string after the strings arrive now fetch the images for those strings.
I believe the discussion is GUI items, not what you might consider data items. Look at this current Hacker News page. Each comment, depending on how fine-grained you're counting, looks to have about 5-6 or more GUI components. In your browser viewport you may see a dozen comments. GUI components add up quickly. These are also only the components in view. React, however, is rendering the entire page. Easily hundreds of React components.
To be clear, I don't want to act like it's not a real concern, I've been in situations where the number of DOM elements I was rendering out with React ended up being a performance concern. It's a real thing. Heck, I've been in situations where React's insistence on computing the virtual DOM was a performance problem. One of the downsides of component-based architecture is that it's very easy to get into situations where you're completely pointlessly duplicating effort parsing data multiple times and passing it around between components and mapping it to new lists of data. It's a major weakness for systems like React that encourage you to loosely couple all of your components, even components that are inherently very related to each other.
But I think people are underestimating what that component/DOM limit is, even for browsers like IE 11.
If you have 100 items on screen, and it's causing the browser to slow down, you'd better be doing something at least somewhat complicated with those elements. You'd better be sticking some SVG charts in there or something. Otherwise I'm going to (perhaps uncharitably) accuse you of overcomplicating your DOM and inserting a bunch of nodes that don't need to exist.
If someone wants to try and implement HN in React, they shouldn't be using 5-6 separate React components for a single comment, that's... I don't know how to parse that kind of architecture. I don't want to be dismissive about it, and different people have different approaches to programming -- but I feel pretty strongly that kind of granularity is over-engineering.
I mean, yes, some of this is dependent on CSS. If you're doing complex rendering, or doing something strange with your fonts. I'm not going to make absolute claims about performance in every case, and different people have different interactions and requirements they're dealing with. There's caveats.
However, if someone comes to me and says, "I put 1000 DOM nodes into the browser and my page froze", I might not immediately make strong claims about the application, but I am going to immediately start looking around the codebase suspiciously to see if they're doing something strange or abnormal.
The current HN page you're on right now has ~2500 DOM nodes. Does it freeze for you when you load the page?
People complain a lot about DOM insertion, but while DOM insertion does sometimes cause problems, I've found it's more often that the problems happened before the insertion. At the point where DOM insertion actually becomes a problem, I suspect we're usually no longer talking about lists of 1000 simple table elements, or just putting 100 search results on a single page.
I am sure I can make a list with just text and not css I can insert 1000 text nodes in it, the issue is if you have something mroe complex like a grid with thumbnails and titles, and a small description under, and some css effect for hover, and a few buttons when you hover over those items etc. In a sane GUI this 1000 widgets are not created at all, only the visible ones are created and when you scroll the data under the Widget is swapped, this is great because you don't have to focus on performance or use pagination and then pretend pagination is better UX.
Just to clarify I agree that there should not be many DOM nodes in a page, the browser or framework widget should be smart and transparently recycle the existing DOM elements, I imagine there would be some new List and Grid widgets that are more advanced but that could be used by everyone with his favorite framework.
As a side note, this gets a little bit at an issue I feel pretty passionate about, which is that HTML is not a language for laying out interfaces, it is a user-facing interface itself. HTML isn't for programmers, it's for users.
The sane GUI you're talking about is essentially virtual rows, they're just baked into many native toolkits. A native interface doesn't insert everything because it doesn't have to, its interface is purely visual. If there are accessibility controls, they happen through a separate interface. If there are keyboard controls, they're based on the widget, not based purely on the content.
This is not a universal opinion, you will find people who disagree with me on this. But I think the web is fundamentally different than that. The web at least attempts to force your text interface and visual interface to be the same.
I'm not sure if it was to you or someone else that I mentioned that many apps (even native ones) are really just interactive documents when you think about their data separate from their styling. In my mind, the innovation of the web is that it acknowledges that, and it forces your interface to be a pure XML-like tree. And then you can put some styling on top of that with CSS if you want to. There are a few exceptions to that, but for the most part, HTML doesn't really let you hide a ton of information.
So imagine if you were building an app in GTK, and GTK said, "okay, first give me a pure-text representation of your interface that I can pipe to a terminal. And then I will allow you to position boxes on the screen, but only from the text that you told me I can pipe to the terminal."
That's a very different paradigm than how most other application frameworks work, and I think that the DOM's insistence of not having a lot of opaque data-bindings where possible makes a lot more sense when viewed through that lens. That lens also helps explain why some devs (myself included) got very annoyed about Google messing with that paradigm because they had their own 'cute' idea for web components.
The downside of this paradigm is that the tools you're talking about like data-bound lists aren't natively built into the DOM. You end up needing to use 3rd-party Javascript libraries for them. But for the most part, due to Javascript's popularity, those libraries are easy to find. Although due to Javascript's popularity, sometimes they are of dubious quality. :)
This thing where each developer re=invents same thing over and over and each implementation is broken in some way it bothers me, like the search input in Youtube has a dropdown with some suggestion, that dropdown popup can get stuck open until you reload the page, so even Google engineers are not capable of implementing a 100% working simple Widget.
Well, I guess that's the web in 2020 in a nutshell, lol. What would you do with a list of 100,000 items? Just slowly scroll them out, one by one while your scroll gets faster and faster? At one point, are you just a billion pixels down? What does "scrolling up" even mean down there? Or do we just limit all lists to 1000 items, period?
Should you limit the files in a folder to some number because the ListWidget has a limit of 1000 ?
No, the solution is too use our brain and realize that you can open giant log files in a text editor , you can have a list with many items in, this problem were solved already the issue is that HTML was designed for documents not applications and the built-in lists or tables are not optimized at all, where a List widget or DataGrid Widget in other toolkits were optimized.
I am not expecting a developer to put some hard work to fix this in his app, browsers or frameworks should do this, I dislike the idea that pagination is good UX as an excuse for "pagination is the cheap solution"
I think it's just easy to make mistakes (bad loops, overly trigger-happy interpretations of "state change", etc), especially for all things "lists" (which make up half the web). Since people make mistakes, I'd always consider easy-to-make mistakes a design flaw, in this case affecting performance.
Also there's a psychological cost to making incremental "loading" operations visible. Although some mysterious "research" is frequently cited that people just fucking LOVE spinning loading icons and gray placeholder boxes that are never where the actual content will pop in, it's just instantly making my perception of a site slower. If a website just takes 5 seconds to load, okay. If, during that time, it just pops in and out 50 components, 3 different loading bars, spinning whirlies and blurry placeholder images (where the one I actually want to see always loads last), I'm suddenly having a "delay experience". It's like a whole firework of bullshit happening to remind you the site HAS NOT loaded yet.
Do you remember Ryan Florence's talk Hype at ReactConf 2015, where he compared React performance to that of Ember and Angular 1 [0]? Back in 2015, in relative terms, React seemed fast!
When you also couple with the fact that at the time Angular 1 was doing things like dirty checking, render performance with the vdom and the ability to sync render updates was definitely a selling point for me.
The apparent motivation behind React seems to have morphed a bit.
For instance I already see that something like Mobx that has observable state doesn't need to re-render everything and still gives you guarantees about not having stale values (thanks to the way observable works with it).
Memoizing a leaf component probably isn't worth doing unless it has some incredibly complex logic in it.
Just like memoizing a single reduce operation probably isn't worth it either.
The React community is the absolute worst offender for premature optimization.
Measure first, then optimize.
I also wrote an article about this, if you can write efficient code without sacrificing readability I think it's worth the (small) effort.
The submitted article is fairly similar in some sense. You address optimization but skip out on the most important step of optimizing - measuring what you need to optimize. Tacking memo, ref and key onto everything isn't free and if those parts of your application weren't the problem you're actually making it slower.
It makes the same claim about code reuse, with the reasoning that code is more likely to be JIT compiled if it's a single hotspot rather than two, well, 'warm spots'. Again it doesn't properly back up this reasoning.
They are also the worst for "no optimization it takes 5 seconds to load" pages.
https://mithril.js.org/lifecycle-methods.html#avoid-prematur...
"You should only use onbeforeupdate to skip diffing as a last resort. Avoid using it unless you have a noticeable performance issue."
From the React docs (https://reactjs.org/docs/react-component.html#shouldcomponen...):
"Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior. [...] This method only exists as a performance optimization."
Both framework developers are telling users the same thing.
I haven't seen that.
> The blog post behind this thread is a prime example.
But it's...not at all an example of what you describe. The blog post explains how things work and how to optimize, it doesn't encourage optimizing without demonstrated need, and the "even better" optimization approach it suggests isn't using memoization or shouldComponentUpdate, but instead paying attention to the basic component structure.
Absolutely not, wrapping everything in shouldComponentUpdates probably leads to worse performance as the comparison checks are more expensive in hot paths than just rendering everything again (same with PureComponents and so on).
This sounds like good advice, but from experience I've found that optimizing code by adding things like memoization after you've discovered it's too slow in the context of an app is a lot harder than than writing something that's efficient to start with, especially if a feature is mature and other parts of the code are resistant to change. There's a balance to strike between spending time writing optimal code and actually shipping, but if there are low-hanging fruit available and you choose not to take advantage of them you'll probably regret it.
It's probably good advice to avoid lots of custom shouldComponentUpdate logic though. That's smelly.
This is contradictory
The whole silly meme of "premature optimization is the root of all evil" is a 100% useless statement. Of course, you shouldn't do it yet if it's premature- that's the definition of premature.
But if it's almost the same amount of effort to write faster code from the get go- especially if you KNOW the code will be speed-sensitive, then you should absolutely try to do the fast thing over the slow thing. That seems obvious...
As someone who spent weeks trying to optimize a react native app, believe me, this is not going to go the way you think it is. It never will. Not without rewriting half of your app and earning the ire of your customers for being a slow hog along the way.
> Memoizing a leaf component probably isn't worth doing
It isn't when you only have 10 components. By the time you have a thousand of them in a big app, you will have a thousand small performance paper cuts in you application. And they will bleed your app while you can do nothing but watch.
But I have used hundreds of components in some apps. Most of them were big enterprise apps that had a lot of input fields for data entry. Given the was data entry works in React, especially with the general form practices, it was a nightmare when I first started and was a follower of the entire premature optimization gospel.
For a more similar and public example, just have a look at how sluggish the jira webapp feels.
Case in point: why are (or were?) anonymous functions frowned upon in React props?
Is it because of the penalty involved with creating a function on each render call? Of course not - my phone can do 19mln of those a second.
It's because the VDOM sees a reference change, so it re-renders the component.
Obviously if there's a performance penalty, it's because of the latter, but I heard the former before as an argument in a discussion.
Makes me wonder why React doesn't work in the same way... using observable state.
Yes.
> I always assumed optimized react was somehow significantly reducing the browser rendering
Let's say you add a <div>. The least amount of rendering you can do, is the render associated with of that new <div>. If you code this manually, you can guarantee you only add the div, and not modify anything else. This is theoretically the best it can be, and so the best React can do as well, from the rendering standpoint.
From the work standpoint, React does many more things associated with stuff that make it work. From the vdom, to the scheduler— it's all overhead React pulls in so it can function. So in holistically in terms performance, adding that <div> yourself is always going to be faster than doing so through React— the less code to run the faster something is.
What React gives you is a framework to think about your application's architecture and execution. Adding a <div>, easy to do manually, no problem. Rendering a whole application, with thousands of possible states, moving parts, some data-fetching here, some animation there, all the while the user is clicking buttons in the middle of state updates— that's harder. At that point can you be sure you're hand-writing the optimal solution, taking into consideration not only _what_ you're rendering, but _when_ it's updating, _how_ the data is changing, and _where_ these mutations are taking place? That's what React gives you. And maybe it doesn't (always) do what's optimal for you, but it does at least provide order to all this complexity, so you can more easily figure it out.
https://johnresig.com/blog/the-dom-is-a-mess/
If the vdom can do the diff and sync all the changes to one DOM update, you only trigger one re-layout. That’s one of the main reasons to have the vdom, to offset shitty DOM performance by using app memory. The browser really sucked/sucks.
I urge everyone to keep track of the history of JS frameworks, and why we even gravitate towards these frameworks.
Could we clear up more the meaning of rendering and how it optimizes the performance mentioned in the article? Suppose the DOM has not changed, is the DOM repainted? How about in the case you type into a textbox which expands this container, causing adjacent elements to cascade into a new position?
Is this something similar to how mobile devices do not have to render content that is "not" displayed on the screen, as in the example of a large ListView so the mobile device does not have to immediate render any content that is expected to be off screen.
React will try to reduce the number of renders to the real DOM as much as possible, so calling a component's render functions doesn't always imply a re-paint of the UI.
Expanding a text box wouldn't usually trigger a re-render because this is being handled by the browser and doesn't affect the state of the application (just like any CSS rules). Reordering elements on the other hand will trigger a re-render because the actual HTML element's position has to change.
So yes, the argument is that the entire screen doesn't have to be repainted. In the ideal case it only repaints the parts of the UI that need to change.
No.