Let’s dive in! Like everyone else, I started my front-end development journey with jQuery. Pure JS-based DOM manipulation was a nightmare back then, so it was what everyone was doing. Then slowly, JavaScript-based frameworks became so prominent that I couldn’t ignore them any longer. The first one I learned was Vue. I had an incredibly hard time because components and state and everything else was a totally new mental model, and it was a lot of pain to fit everything in. But eventually, I did, and pat myself on the back. Congratulations, buddy, I told myself, you’ve made the steep climb; now, the rest of the frameworks, should you ever need to learn them, will be very easy. So, one day, when I started learning React, I realized just how terribly wrong I was. Facebook didn’t make things any easier by throwing in Hooks and telling everyone, “Hey, use this from now on. But don’t rewrite classes; classes are fine. Actually, not so much, but it’s okay. But Hooks are everything, and they are the future. Got it? Great!”. Eventually, I crossed that mountain, too. But then I was hit by something as important and difficult as React itself: rendering. If you have come across rendering and its mysteries in React, you know what I’m talking about. And if you haven’t, you have no idea what’s in store for you! 😂 But before wasting time on anything, it’s a good habit to ask what you’d gain from it (unlike me, who’s an overexcited idiot and will happily learn anything just for the sake of it 😭😭). If your life as a React dev is moving fine without worrying about what this rendering is, why care? Good question, so let’s answer this first, and then we’ll see what rendering actually is.
Why is understanding rendering behavior in React important?
We all begin learning React by writing (these days, functional) components that return something called JSX. We also understand that this JSX is somehow converted into actual HTML DOM elements that show up on the page. The pages update as the state updates, the routes change as expected, and all is fine. But this view of how React works is naive and a source of many problems. While we often succeed in writing complete React-based apps, there are times when we find certain parts of our application (or the entire application) remarkably slow. And the worst part . . . we have not a single clue why! We’ve done everything correctly, we see no errors or warnings, we’ve followed all the good practices of component design, coding standards, etc., and no network slowness or expensive business-logic computation is going on behind the scenes. 🤔 Sometimes, it’s a totally different problem: there’s nothing wrong with the performance, but the app behaves weirdly. For example, making three API calls to the authentication backend but only one to all others. Or some pages are getting redrawn twice, with the visible transition between the two renders of the same page creating a jarring UX. Worst of all, there’s no external help available in cases like these. If you go to your favorite dev forum and ask this question, they’ll answer, “Can’t tell without looking at your app. Can you attach a minimum working example here?” Well, you, of course, can’t attach the entire app for legal reasons, while a tiny working example of that part may not contain that problem because it’s not interacting with the entire system the way it is in the actual app. Screwed? Yeah, if you ask me. 🤭🤭 So, unless you want to see such days of woe, I suggest you develop an understanding — and interest, I must insist; understanding gained reluctantly won’t get you far in the React world — in this poorly understood thing called rendering in React. Trust me, it’s not that hard to understand, and though it’s very difficult to master, you’ll go really far without having to know every nook and cranny.
What does rendering mean in React?
That, my friend, is an excellent question. We don’t tend to ask it when learning React (I know because I didn’t) because the word “render” perhaps lulls us into a false sense of familiarity. While the dictionary meaning is completely different (and it’s not important in this discussion), we programmers already have a notion of what it should mean. Working with screens, 3D APIs, graphics cards, and reading product specs trains our minds to think of something along the lines of “paint a picture” when we read the word “render”. In game-engine programming, there’s a Renderer, whose sole job is to — precisely!, paint the world as handed over by the Scene. And so we think that when React “renders” something, it collects all the components and repaints the DOM of the web page. But in the React world (and yes, even in the official documentation), that isn’t what rendering is about. So, let’s tighten our seat belts and take a real deep(ish) dive into the React internals. You must have heard that React maintains what’s called a virtual DOM and that it periodically compares it with the actual DOM and applies changes as necessary (this is why you can’t just throw in jQuery and React together — React needs to take full control of the DOM). Now, this virtual DOM isn’t composed of HTML elements as the real DOM does, but of React elements. What’s the difference? Good question! Why not create a small React app and see for ourselves? I created this very simple React app for this purpose. The entire code is just a single file containing a few lines: Notice what we’re doing here? Yes, simply logging what a JSX element looks like. These JSX expressions and components are something we’ve written hundreds of times, but we seldom pay attention to what’s going on. If you open your browser’s dev console and run this app, you’ll see an Object that expands to: This might look intimidating, but take note of a few interesting details:
What we’re looking at is a plain, regular JavaScript object and not a DOM node.Notice that the property props says that it has a className of App (which is the CSS class set in the code) and that this element has two children (this matches too, the child elements being the
andtags).The _source property tells us wherein the source code does the element’s body start. As you can see, it names the file App.js as the source and mentions line number 6. If you look at the code again, you’ll find that line 6 is right after the opening JSX tag, which makes sense. The JSX parentheses contain the React element; they aren’t part of it, as they serve to transform into a React.createElement() call later.The proto property tells us that this object derives all its. properties from the root JavaScript Object, again reinforcing the idea that it’s just everyday JavaScript objects we’re looking at here.
So, now, we understand that the so-called virtual DOM doesn’t look anything like the real DOM but is a tree of React (JavaScript) objects representing the UI at that point in time. Exhausted? Trust me, I’m too. 🙂 Turning these ideas over and over in my head to try and present them in the best way possible, and then think of the words to bring them out and rearrange them — isn’t easy. 😫 But we’re getting distracted! Having survived this far, we’re now in the position to answer the question we were after: what is rendering in React? Well, rendering is the React engine process walking through the virtual DOM and collecting the current state, props, structure, desired changes in the UI, etc. React now updates the virtual DOM using some calculations and also compares the new result with the actual DOM on the page. This calculating and comparing is what the React team officially calls “reconciliation”, and if you’re interested in their ideas and relevant algorithms, you can check the official docs.
Time to Commit!
Once the rendering part is done, React starts a phase called “commit”, during which it applies the necessary changes to the DOM. These changes are applied synchronously (one after the other, though a new mode that works concurrently is expected soon), and the DOM is updated. Exactly when and how React applies these changes isn’t our concern, as it’s something that’s totally under the hood and likely to keep changing as the React team tries out new things.
Rendering and performance in React apps
We’ve understood by now that rendering means collecting info, and it doesn’t need to result in visual DOM changes every time. We also know that what we consider as “rendering” is a two-step process involving rendering and commit. We’ll now see how rendering (and more importantly, re-rendering) is triggered in React apps and how not knowing the details can cause apps to perform poorly.
Re-rendering due to change in parent component
If a parent component in React changes (say, because its state or props changed), React walks the entire tree down this parent element and re-renders all components. If your application has many nested components and a lot of interactions, you’re unknowingly taking a huge performance hit every time you change the parent component (assuming it’s just the parent component you wanted to change). True, rendering won’t cause React to change the actual DOM because, during reconciliation, it will detect that nothing has changed for these components. But, it’s still CPU time and memory wasted, and you’d be surprised how quickly it adds up.
Re-rendering due to change in Context
React’s Context feature seems to be everybody’s favorite state-management tool (something it wasn’t built for at all). It’s all so convenient — just wrap the topmost component in the context provider, and the rest is a simple matter! The majority of React apps are being built like this, but if you’ve read this article so far, you’ve likely spotted what’s wrong. Yes, every time the context object is updated, it triggers a massive re-rendering of all tree components. Most apps have no performance awareness, so nobody notices, but as said before, such oversights can be very costly in high-volume, high-interaction apps.
Improving React rendering performance
So, given all this, what can we do to improve our apps’ performance? It turns out there are a few things that we can do, but take note that we’ll only be discussing in the context of functional components. Class-based components are highly discouraged by the React team and are on their way out.
Use Redux or similar libraries for state-management
Those who love the quick-and-dirty world of Context tend to hate Redux, but this thing is hugely popular for good reasons. And one of these reasons is performance — the connect() function in Redux is magical as it (almost always) correctly renders only those components as necessary. Yes, just follow the standard Redux architecture, and performance comes free. It’s not an exaggeration at all that if you adopt the Redux architecture, you avoid most of the performance (and other) problems right away.
Use memo() to “freeze” components
The name “memo” comes from Memoization, which is a fancy name for caching. And if you’ve not come across caching much, it’s okay; here’s a watered-down description: every time you need some computation/operation result, you look in the place where you’ve been maintaining previous results; if you find it, great, simply return that result; if not, go ahead and perform that operation/computation.
Before diving straight into memo(), let’s first see how unnecessary rendering occurs in React. We begin with a straightforward scenario: a tiny part of the app UI that shows the user how many times they’ve liked the service/product (if you’re having trouble accepting the use case, think of how on Medium you can “clap” multiple times to show how much you support/like an article).
There’s also a button that allows them to increase the likes by 1. And finally, there’s another component inside that shows the users their basic account details. Don’t worry at all if you’re finding this hard to follow; I’ll now provide step-by-step code for everything (and there isn’t much of it), and at the end, a link to a playground where you can mess with the working app and improve your understanding.
Let’s first tackle the component about customer info. Let’s create a file called CustomerInfo.js that contains the following code:
Nothing fancy, right?
Just some informational text (which could have been passed through props) that is not expected to change as the user interacts with the app (for the purists out there, yes, sure it can change, but the point is, when compared to the rest of the application, it’s practically static). But do notice the console.log() statement. This will be our clue to know that the component was rendered (remember, “rendered” means its info was collected and calculated/compared, and not that it was painted onto the actual DOM).
So, during our testing, if we see no such message in the browser console, our component wasn’t rendered at all; if we see it appear 10 times, it means the component was rendered 10 times; and so on.
And now let’s see how our main component uses this customer info component:
So, we see that the App component has an internal state-managed through the useState() hook. This state keeps counting how many times the user has liked the service/site, and is initially set to zero. Nothing challenging as far as React apps go, right? On the UI side, things look like this:
The button looks too tempting not to be smashed, at least to me! But before I do that, I’ll open my browser’s dev console and clear it. After that, I’m going to smash the button a few times, and here’s what I see:
I’ve hit the button 19 times, and as expected, the total likes count sits at 19. The color scheme was making it really hard to read, so I added a red box to highlight the main thing: the
“Intelligent” component design
I put “intelligent” in quotes because: 1) Intelligence is highly subjective and situational; 2) Supposedly intelligent actions often have unpleasant consequences. So, my advice for this section is: don’t be too confident in what you’re doing. With that out of the way, one possibility of improving rendering performance is to design and place components a little differently. For example, a child component can be refactored and moved to somewhere up the hierarchy so as to escape re-renders. No rule says, “the ChatPhotoView component must always be inside the Chat component”. In special cases (and these are cases where we have data-backed evidence that performance is being affected), bending/breaking the rules can actually be a great idea.
Conclusion
Much more can be done to optimize React apps in general, but since this article is on Rendering, I’ve restricted the discussion’s scope. Regardless, I hope you now have a better insight into what’s going on in React under the hood, what rendering actually is, and how it can affect application performance. Next, let’s understand what is React Hooks?