React Re-Renders

Serhii Shramko /
5 min read • --- views

Hi, react Andy!
I’ve used React for over six years. I’ve noticed that even skilled programmers often struggle to grasp how React operates behind the scenes. This can lead to inefficiencies. It affects code quality, speed, and user experience.
The documentation is great for starting with React. Many books, courses, and blogs focus on beginners. But what comes next? How can you dive deeper and really understand how it works? If you’ve been writing React for a while, basic or even intermediate courses may not be enough. Sadly, resources for advanced learning are limited.
That’s why I’m launching a series of articles to fill this knowledge gap.
Intro to re-renders
Understanding re-renders in React is crucial for performance. You need to understand what triggers them. Know how they move through the app. Learn what happens during a re-render and why it matters.

The problem
Imagine you’re an intern at a FAANG company. Your first task is to create a new React component. You’re asked to add a
simple button
that opens a modal dialog
at the top of the app.
const App = () => {
// Some code here
return <div className="layout">
{/* The button should go somewhere here */}
<VerySlowComponent />
<AnotherComponent />
<OtherStuff />
</div>;
};
Then you implement it. The task seems trivial. We've all done it hundreds of times:
const App = () => {
// Add state
const [isOpen, setIsOpen] = useState(false);
// Everything that is returned here will be re-rendered when the state is updated
return <div>
{/* Add the button */}
<Button onClick={() => setIsOpen(true)}>
Open dialog
</Button>
{/* Add the dialog */}
{isOpen && <ModalDialog onClose={() => setIsOpen(false)} />}
<VerySlowComponent />
<AnotherComponent />
<OtherStuff />
</div>;
};
You add state to track if the dialog is open or closed. You include a button to change the state and render the dialog based on this state.
When you run the app, there’s a noticeable delay ⏰, almost a second — before the dialog appears.
Experienced React developers might quickly suggest.
"You’re re-rendering the whole app. Just wrap everything in React.memo and use useCallback to avoid unnecessary renders."
This advice has its place, but it’s wise to pause before jumping in. In this case, memoization isn’t needed and could hurt performance.
First, let’s take a closer look at what exactly is happening and why this delay occurs.

When we click the button, we trigger the setIsOpen
setter function, which updates the isOpen
state from false
to
true
. As a result, the App
component that holds this state re-renders itself.
After the state updates and the App
component re-renders, React must pass the new data to other dependent components.
It automatically re-renders all components that the first component shows. It keeps going down the tree until it reaches
the end.
If you visualize a typical React app as a tree structure, you will find that everything beneath the point where the state update was initiated will be re-rendered.
Re-rendering is crucial to understand in React.
The key point to remember is that React never re-renders components "up" the render tree. If a state update occurs in the middle of the component tree, only the components "down" the tree will be re-rendered.

When a component is wrapped in React.memo
, React
will interrupt its default re-rendering process and first evaluate
whether the props
have changed. If there are no changes to the props
, re-renders will be halted. However, if even a
single prop
is modified, the re-rendering will proceed as usual.
It's important to note that effectively preventing re-renders through memoization is a nuanced topic with various considerations. For a deeper understanding, it is advisable to explore these concepts further in new articles. (Comming soon...)

Wrapping components with React.memo
can indeed help prevent unnecessary re-renders in certain scenarios. However, it's
important to note that using React.memo
comes with its own set of complexities and caveats, which will be discussed in
future articles. A more effective approach is to isolate the components that rely on specific state and encapsulate both
the state and those components into a smaller, dedicated component. This can lead to improved performance and a clearer
component structure.
Moving state down
Let's move our logic in separated component.
const ButtonWithModalDialog = () => {
const [isOpen, setIsOpen] = useState(false);
return <>
<Button onClick={() => setIsOpen(true)}>
Open dialog
</Button>
{isOpen && <ModalDialog onClose={() => setIsOpen(false)} />}
</>;
};
And then render this new component in the App
:
const App = () => {
return <div>
// Our new component
<ButtonWithModalDialog />
<VerySlowComponent />
<AnotherComponent />
<OtherStuffComponent />
</div>;
};

Consequently, the modal dialog appears immediately. We resolved a significant performance issue using a straightforward composition technique!
Share it: