Parents & Owners in React: Rendering Performance

Being aware of the distinction between parent and owner components can help you isolate updates and improve rendering performance.

Published · Last updated

In React, the distinction between parent and owner components is fundamental, important, and very under-discussed.

  • The owner is the component that renders a particular component.
  • The parent is the component (or element) in which that particular component is nested.

Once you’re aware of it, you’ll start seeing it everywhere. You’ll notice how overloaded the word ‘parent’ is in React and how people really mean ‘owner’ when they say ‘parent’.

Parents vs Owners Quiz

If this is unclear, read my previous post Parents & Owners in React: Data Flow and do the quiz here.

Look at this codeblock:


function Post() {
return (
<Article title="Parent vs Owner">
<Section title="Data Flow">
<p>Content</p>
</Section>
</Article>
);
}
function Article({ children }) {
return (
<article>
<h1>{title}</h1>
{children}
</article>
);
}
function Section({ children, title }) {
return (
<section>
<h2>{title}</h2>
{children}
<Callout />
</section>
);
}
function Callout() {
return (
<aside>
<strong>Subscribe</strong>
</aside>
);
}

Now try to answer these questions:

Which component is the parent of <Section>?

Which component is the owner of <Section>?

Which prop is a React element?

Which component is passed down as a React element?

Which component are slotted components?

Data flow (props) follows the owner or parent tree (select one)

And here are the diagrams for both the trees:

Post (1)PostArticle (2)Post/Articlearticle (3)Post/Article/articleh1 (4)Post/Article/article/h1Section (5)Post/Article/article/Sectionsection (6)Post/Article/article/Section/sectionh2 (7)Post/Article/article/Section/section/h2p (7)Post/Article/article/Section/section/pCallout (8)Post/Article/article/Section/section/Calloutaside (9)Post/Article/article/Section/section/Callout/asidestrong (10)Post/Article/article/Section/section/Callout/aside/strong
Parent tree
Post (1)PostArticle (2)Post/ArticleSection (5)Post/Sectionp (7)Post/particle (3)Post/Article/articleh1 (4)Post/Article/h1section (6)Post/Section/sectionh2 (7)Post/Section/h2Callout (8)Post/Section/Calloutaside (9)Post/Section/Callout/asidestrong (10)Post/Section/Callout/strong
Owner tree

If you had trouble answering these questions. I recommend you read the previous post before reading on. It’s important to get this difference right.

Ideally, the UI of our React apps is snappy and the code for it easy to follow. How can being aware of this distinction help with this? In the previous post Parents & Owners in React: Data Flow, we saw how being aware of the parent/owner difference can help make data flow of a React app easier to follow. But what does it have to do with rendering performance and snappy UI’s?

To answer this question, we must first answer another, more fundamental question.

A component is most commonly described as a “piece of the UI”. It’s not a bad way to think about it, but not particularly helpful for rendering performance. It doesn’t reflect the amount of work done to create that part of the UI and keep it up-to-date. Keep in mind that re-renders generally happen more than mount and unmount.

For performance, it helps to think of a React component as a unit of update. A component can schedule an update (a re-render) by changing state. It’s a rather technical definition but a very solid one.

At its core, React is essentially a library for processing a queue of state updates to produce consistent UIs.

If we view a component as a unit-of-update, we can view putting components together as dividing the work for keeping the UI up-to-date. If we’re mindful of how we divide the work to update, we could do less work updating and improve rendering performance.

Why do we even split apps into components? First, it’s useful to turn it around it and imagine how we can make performance worse. We could write a React app as a single component. All JSX and state packed in to one. Every change to state would re-render everything. We’ll have just one big unit-of-update, without division of work.

It’s easy to see why we don’t. React shouldn’t need to update UI unrelated to a particular change of data. Components provide a way to split the UI into parts that should be updated. A component only updates itself and the components it renders; the element tree it owns, that’s the unit-of-update.

Which brings me to another important distinction. There are two reasons for a component to re-render :

  1. Self-update: React will call the component whose state update triggered the render.
  2. Owner-update: By default, when a component re-renders, all of the components it renders (owns) are called too. This process is recursive, updates (re-renders) cascade down the owner tree.

I’ll reuse the example from the previous post. currentUser state is placed in <App>. I’ve added this state to the diagram and made it interactive too. Go ahead and click currentUser it to what updating it does.

Trigger currentUser state updatecurrentUserAppApp (3)App/divdiv (4)App/div/HeaderHeader (5)App/div/Header/headerheader (6)App/div/Header/header/h2h2 (7)App/div/divdiv (8)App/div/div/DashboardDashboard (9)App/div/div/Dashboard/mainmain (10)App/div/div/Dashboard/main/h2h2 (11)App/div/div/Dashboard/main/DashboardNavDashboardNav (12)App/div/div/Dashboard/main/DashboardNav/navnav (13)App/div/div/Dashboard/main/DashboardNav/nav/h3h3 (14)App/div/div/Dashboard/main/DashboardContentDashboardContent (15)App/div/div/Dashboard/main/DashboardContent/divdiv (16)App/div/div/Dashboard/main/DashboardContent/div/h3h3 (17)App/div/div/Dashboard/main/DashboardContent/div/WelcomeMessageWelcomeMessage (18)App/div/div/Dashboard/main/DashboardContent/div/WelcomeMessage/divdiv (19)App/div/div/Dashboard/main/DashboardContent/div/WelcomeMessage/div/pp (20)App/div/FooterFooter (23)App/div/Footer/footerfooter (24)App/div/Footer/footer/h2h2 (25)

Q: Which component self-updates?

Slotted components have a ‘hole in their middle’. They take React elements as props and return them. This way they let other components place elements inside of them. This makes it possible to change which component “owns” a particular element without affecting the DOM structure and UI.

What makes slotted components really neat is that they enable a built-in rendering optimization. A passed-down element (children or JSX-as-named-props) is created by the owner, not the parent. If the parent re-renders by a self-update, the owner does not, meaning the passed-down element is not recreated.
It is the same element (as in referentially equal) as last render. React knows there is no point in rendering it and skips it .

We can use this to untangle components into parts that change and parts that don’t change, leading to smaller units-of-update. Then we can combine these parts back together to keep the same parent structure/UI. This is how slotted components enable splitting the work for updates and provide a natural optimization opportunity .

A less natural optimization opportunity is to wrap child elements in useMemo. This works for the same reason but is much less common.

It’s hard coming up with examples and even harder to come up with a better one than than the one from Before You memo by Dan Abramov, so I’ll just use that one. I recommend you read that post, it’s really good and not that long.

App owns ExpensiveTree and contains color state. Updating color will trigger a re-render of App which in turn will render ExpensiveTree, which has no need for it.

color state is placed too high and therefore App does too much work updating.


function App() {
const [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
//

We need to untangle state — and by extension, updates — from parts of the UI that don’t need it.

If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component.

First, make a new, slotted component to hold the often-updated state.


function ColorPicker({ children }) {
const [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
{children}
</div>
);
}
//

Then, seperate the elements irrelevant to that state, <p> and <ExpensiveTree />, and pass them down as children.


function ColorPicker({ children }) {
const [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
{children}
</div>
);
}
function App() {
return (
<ColorPicker>
<p>Hello, world!</p>
<ExpensiveTree />
</ColorPicker>
);
}

App owns ExpensiveTree and contains color state. Updating color will trigger a re-render of App which in turn will render ExpensiveTree, which has no need for it.

color state is placed too high and therefore App does too much work updating.

We need to untangle state — and by extension, updates — from parts of the UI that don’t need it.

If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common parent component.

First, make a new, slotted component to hold the often-updated state.

Then, seperate the elements irrelevant to that state, <p> and <ExpensiveTree />, and pass them down as children.


function App() {
const [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p>Hello, world!</p>
<ExpensiveTree />
</div>
);
}
//

Before
Trigger color state updatecolorAppApp (1)App/divdiv (2)App/div/inputinput (3)App/div/pp (4)App/div/ExpensiveTreeExpensiveTree (5)App/div/ExpensiveTree/pp (6)Trigger color state updatecolorAppApp (1)App/divdiv (2)App/inputinput (3)App/pp (4)App/ExpensiveTreeExpensiveTree (5)App/ExpensiveTree/pp (6)
After
AppApp (1)Trigger color state updatecolorApp/ColorPickerColorPicker (2)App/ColorPicker/divdiv (3)App/ColorPicker/div/inputinput (4)App/ColorPicker/div/pp (5)App/ColorPicker/div/ExpensiveTreeExpensiveTree (6)App/ColorPicker/div/ExpensiveTree/pp (7)AppApp (1)Trigger color state updatecolorApp/ColorPickerColorPicker (2)App/ColorPicker/divdiv (3)App/ColorPicker/inputinput (4)App/pp (5)App/ExpensiveTreeExpensiveTree (6)App/ExpensiveTree/pp (7)

Be sure to click the state to trigger a re-render.

See how updating color only triggers a self-update of <ColorPicker> and only updates relevant parts of the UI now? <App> contains no state, so <p> and <ExpensiveTree> will never be re-rendered by <App>.

memo let’s you opt out of an owner-update by doing an extra check to see if any of the component’s props have changed. Basically, it turns the components props in to a dependency list for owner-updates.

These techniques [moving state down and using slotted components to lift UI] are complementary to what you already know! They don’t replace memo or useMemo, but they’re often good to try first.

Memoization is contagious. Props passed to a memoized component need to be referentially equal across renders . You need to apply useCallback & useMemo to all unstable props for memo to work, which might be a long chain of values up the tree.

That’s where it’s easy to go wrong, one unstable value can bust memo. In which case, React is doing more work than before, with nothing gained.

There’s a time and place for memo but it’s a fickle fix. Don’t be careful before it’s necessary. Only optimise for rendering performance when it’s a measurable issue. Find chokepoints that you’re certain memo can solve, then apply it.

Before you apply optimizations like memo or useMemo, it might make sense to look if you can split the parts that change from the parts that don’t change. The interesting part about these approaches is that they don’t really have anything to do with performance, per se. Using the children prop to split up components usually makes the data flow of your application easier to follow and reduces the number of props plumbed down through the tree. Improved performance in cases like this is a cherry on top, not the end goal.

If you structure your components in such a way that the parts of the UI are aligned with the updates, the updates will be more granular and less work will be done. You may never even need to use memo.

A related and lesser-known pitfall is that slotted components don’t work with memo . children is not really different than any other prop, it’s passed-down data too. The React element that is passed-down (the JSX that the component wraps) is recreated whenever the owner re-renders. Remember, one new prop is enough for memo to go ahead and re-render, so that busts it.

This is not a very common issue but easy to miss and not mentioned in the docs. You can get around it by memoizing the child element from the owner’s side with useMemo.


function User({ name, data, here }) {
const content = useMemo(() => <p>Hello, {name}.</p>, [name]);
return (
<SlottedMemo more={data} passed={here}>
{content}
</SlottedMemo>
);
}

Dual-memoization!

Slotted components are decoupled from their content. Last post, we saw that how that can make data flow easier to follow. This post, we’ve seen how slotted components can be leveraged to avoid re-renders in parts of the UI that don’t need to be updated.

A component and the tree it owns can be viewed as a unit-of-update. Slotted components make the tree a component owns smaller and therefore the units-of-update smaller too. If state/updates are aligned with relevant parts of the UI, less work is done re-rendering and improved performance can come naturally.

Thanks to Dan Abramov for the example.

  1. After you trigger a render, React calls your components to figure out what to display on screen. “Rendering” is React calling your components. On initial render, React will call the root component. For subsequent renders, React will call the function component whose state update triggered the render.
    This process is recursive: if the updated component returns some other component, React will render that component next, and if that component also returns something, it will render that component next, and so on. The process will continue until there are no more nested components and React knows exactly what should be displayed on screen.
    React Docs: Render and Commit — Step 2: React renders your components
  2. There’s also a lesser-known technique as well: if a React component returns the exact same element reference in its render output as it did the last time, React will skip re-rendering that particular child. There's at least a couple ways to implement this technique: If you include props.children in your output, that element is the same if this component does a state update If you wrap some elements with useMemo(), those will stay the same until the dependencies change.
    Mark's Dev Blog: Blogged Answers A (Mostly) Complete Guide to React Rendering Behavior - Component Render Optimization Techniques
  3. This is a little known React optimization pattern. React re-renders this [slotted] component deeply when [the state updater] is called, and since children can't have changed, it automatically bails out of trying to update the children. No need for [...] memo, etc.
    Sebastian Markbåge tweet
  4. [Passing down elements is] both natural for composition and acts as an optimization opportunity.
    Dan Abramov Tweet
  5. Optimizing with memo is only valuable when your component re-renders often with the same exact props, and its re-rendering logic is expensive. If there is no perceptible lag when your component re-renders, memo is unnecessary. Keep in mind that memo is completely useless if the prop passed to your component are always different, such as if you pass an object or a plain function defined during rendering. This is why you will often need useMemo and useCallback together with memo.
    React Docs: memo - Should you add memo everywhere?
  6. Conceptually, we could say that the difference between these two approaches is: React.memo(): controlled by the child component. Same-element references: controlled by the parent component
    Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior - Component Render Optimization Techniques
  7. Avoid using React.memo on components that use props.children! Those children will almost certainly change every render, so React.memo will never prevent the component from re-rendering, making it unnecessary overhead ✨. Demo: CodeSandbox
    Brandon Dail Tweet
  8. It’s an obvious issue in retrospect: React.memo() shallowly compares the new and the old props and short-circuits the render lifecycle if they’re the same, and the children prop isn’t special, so passing newly created React elements (so any JSX that isn’t specifically persisted) as children will cause a re-render. However, it’s not always easy to connect the dots in practice; for example, I arrived at this issue when I added children to a previously memoized component and noticed that it would re-render unexpectedly.
    Why using the children prop makes React.memo() not work - Reinis Ivanovs (slikts)