Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome to your first lesson. To kick off our journey into React performance and testing, we'll start by building a solid conceptual foundation. This lesson focuses on how React updates the user interface, breaking the process down into its core "render" and "commit" phases.
Understanding this mechanism is crucial because the performance problems you've encountered, like unnecessary re-renders, are symptoms of what's happening during these phases. By the end of this lesson, you'll be able to explain how React's internal workings translate into the responsiveness—or sluggishness—that a user experiences on a page. This knowledge is the first step toward diagnosing and fixing those issues effectively.
The Lifecycle of a UI Update
At its heart, every update you see on the screen in a React application follows a three-step process:
- Trigger: An update is initiated. This usually happens as a result of a user interaction (like a button click) or a programmatic change (like data arriving from an API), which leads to a state change via a
useStatesetter function. - Render: React calls your components to determine what the UI should look like now.
- Commit: React applies the calculated changes to the browser's DOM.
While the trigger is what starts the process, the performance of your application is largely determined by the work done in the render and commit steps. Let's look at these two in more detail.
Step 1: The Render Phase
The term "rendering" in React can sometimes be a source of confusion. It does not mean that React is immediately changing what's on the screen. Instead, as the official documentation puts it, "rendering" is React calling your components.
When state changes in a component, React queues a new render. It then starts from that component and works its way down the component tree, executing the code in each component to generate a new description of the UI. This description is often called the "virtual DOM"—a lightweight representation of the UI held in memory.
A critical point to understand, especially given your focus on list performance, is that this process is recursive by default.
A (Mostly) Complete Guide to React Rendering Behavior
In this highly-regarded article by Mark Erikson, we'll focus on the default rendering behavior. As you read, pay close attention to the core principle it explains about parent and child components.
In the "Standard Render Behavior" section, please read the text from the key takeaway about recursive rendering. The author emphasizes this point because it's the root cause of many performance puzzles in React.
This default behavior is fundamental. If a parent component re-renders, React will, by default, also re-render all of its children, and their children, and so on. It does this even if the props passed to those children haven't changed. React must render them to know if their output is different from the last time.
This isn't necessarily a bad thing—it's how React ensures the UI stays consistent with the application's state. But it's also why a state update high up in the component tree can trigger a lot of work, leading to the kind of unnecessary re-renders you've seen on list-heavy pages.
Step 2: The Commit Phase
After the render phase is complete, React has two versions of the UI description in memory: the one from the previous render and the new one it just created. Now, the commit phase begins.
During this phase, React performs a "diffing" algorithm (also known as reconciliation) to compare the two virtual DOM trees and identify the exact changes that need to be made to the actual browser DOM. It then applies these changes in a single, synchronous batch.
A key optimization is that React only modifies the DOM if there's a difference. If a component re-renders but produces the exact same output as before, React won't touch the corresponding DOM nodes.
The official React documentation provides a clear, simple explanation of the commit phase.
Please read the section titled "React commits changes to the DOM." The Clock example with the <input> element is a perfect illustration of how rendering doesn't always lead to a DOM update, which is a crucial concept for performance.
The distinction between the render and commit phases is vital for performance tuning:
- The Render Phase involves running your component's JavaScript code. This is usually very fast, but can become slow if components are doing heavy computations or if a single update triggers a cascade of thousands of component re-renders.
- The Commit Phase involves DOM manipulation. This is typically slower than running JavaScript. Changing the structure, attributes, or content of DOM elements forces the browser to do more work, which can be a significant performance bottleneck, especially when many elements are updated at once.
To visualize how these pieces fit together, the following video provides a helpful walkthrough of the entire component lifecycle.
The React Lifecycle: Simply Explained!
This video from Cosden Solutions draws out the component lifecycle. We'll focus on the initial render (mounting) and subsequent updates (re-rendering) to see how the render and commit phases manifest in both scenarios.
Watch the section explaining the initial render, paying attention to how it moves from running the function code to updating the DOM. Then, watch the section on the update step to see how similar the process is for re-renders. Notice how in both cases, the "render" (running the function) happens before the "commit" (updating the DOM and what the user sees).
From Commits to Visible Symptoms
The final step in the process isn't actually part of React—it's the browser's job. After React commits its changes to the DOM, the browser must then paint those changes to the screen.
This diagram from the React team shows the full sequence: a trigger causes React to render and commit, after which the browser paints the result.
A user perceives an interaction as "slow" or "janky" when the entire pipeline—from the initial trigger through React's render and commit, to the final browser paint—takes too long. For an animation or interaction to feel smooth, the browser needs to update the screen at a consistent rate, ideally around 60 frames per second (FPS).
Stop worrying about React Re-renders so much (an overview of the react profiler with refactoring)
In this video, Web Dev Cody gives a practical overview of how rendering and committing relate to perceived performance and provides a useful benchmark.
First, watch the brief overview of render vs. commit, where he highlights that DOM manipulation in the commit phase is often the slower part. Then, skip ahead to his discussion on what feels slow.
As mentioned in the video, at 60 FPS, the browser has about 16.7 milliseconds to complete all the work for a single frame. A good rule of thumb, drawing from game development, is that if an update takes longer than about 33 milliseconds (corresponding to 30 FPS), it's likely to be perceived as a noticeable delay or stutter. This is the tangible symptom of an expensive render or commit phase.
Conclusion
In this lesson, we've unpacked the core mechanics of how React updates the UI. Let's summarize the key takeaways:
- Render vs. Commit: Rendering is the process of React calling your components to get a description of the UI. Committing is the subsequent process of applying any differences to the actual DOM.
- Recursive Renders: By default, when a component renders, all its descendants render too. This is a primary cause of performance issues in deeply nested or list-heavy applications.
- Performance Bottlenecks: Slowness can arise from an expensive render phase (too many components re-rendering) or an expensive commit phase (too many DOM nodes being updated). DOM manipulation is often the costlier of the two.
- The 33ms Rule: An interaction that takes longer than roughly 33ms to render, commit, and paint is likely to feel sluggish to the user. This provides a concrete target for our optimization efforts.
Now that we have this theoretical framework, our next step is to put it into practice. In the following lesson, you will learn how to define a repeatable interaction scenario within our sample project. This will allow us to establish a consistent performance baseline, which is the first practical step toward measuring and improving it.