I thought I understood..
I thought I understood React Rerenders
Get ready to revisit your sitting room floor plan.
The human explanation
Imagine you want to rearrange your sitting room. You could immediately start dragging a massive five-seater couch across the floor and arguing with your partner about whether it looks better beside the window (been there).
Or, you could sketch the room on paper, cut out little furniture shapes, and test different layouts first.
That second approach is much cheaper, faster, and easier to experiment with before committing to physically moving everything.
The technical explanation
React rendering works in a surprisingly similar way. Before making expensive updates to the real DOM, React first calculates what the UI should look like and compares the differences.
At a very simplified level, React runs the component function again, compares the new output against the previous version (the reconciliation process), and then commits only the necessary updates to the DOM.
This distinction matters more than I originally realised. A rerender does not automatically mean React is rebuilding the entire page or recreating every DOM node from scratch. Most of the time, React is simply recalculating what the UI should look like and checking whether anything meaningful actually changed.
Under the hood, React uses an internal architecture called Fiber, which helps React split rendering work into smaller chunks, prioritize updates, and batch multiple state changes together into a single render cycle. This is a big part of why React apps can stay responsive even when a lot is happening behind the scenes.
One extra thing to note, is that parent rerenders naturally flow down into child components too. That doesn’t automatically mean React is updating the DOM everywhere, but it does mean React is recalculating those child components again by default.
This is also where things like React.memo, useMemo, and useCallback come in. React heavily relies on referential equality checks ({} !== {}) when deciding whether work can be skipped. Two objects can contain identical values, but if they’re recreated on every render, React still sees them as different references.
That’s why memoization can sometimes help, but also why overusing it can make code more complicated than the problem you were trying to solve in the first place.
Why this matters
One of the biggest misconceptions I had learning React was assuming that every rerender meant React was aggressively tearing apart and rebuilding the whole UI.
In reality, most rerenders are more like React double-checking the furniture plan.
The expensive part is usually the commit phase, actually updating the DOM, recalculating layouts, repainting pixels, loading images, running expensive calculations, and all the browser work that comes afterwards. React is actually pretty good at avoiding unnecessary work there.
This completely changed how I thought about optimization. I used to think the goal was simply to stop rerenders at all costs. But most of the time, the real goal is reducing unnecessary work overall.
The real talk
React rendering is not the enemy.
Renders are mostly just recalculations. Expensive renders are the problem.
I think a lot of frontend developers (myself included) become slightly obsessed with the red flashes in React Profiler tools before understanding whether those rerenders are actually expensive enough to matter in the first place.
From real-world experience, the biggest frontend performance wins I’ve worked on rarely came from aggressively trying to prevent every rerender possible. They usually came from reducing JavaScript bundle size, improving loading strategy, fixing effect loops, image optimization, avoiding huge context rerenders, reducing expensive calculations, and removing unnecessary work higher up the component tree.
In other words, architecture, loading strategy, and reducing unnecessary work tend to matter a lot more.