The mystery of React Element, children, parents and re-renders
Tags: javascript, react, webdev, performance
Published: 2022-07-11 10:02:39


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/we5bcchguv2qu7ta9ofi.png)


Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

---

In one of the previous articles about <a href="https://www.developerway.com/posts/components-composition-how-to-get-it-right">React composition</a>, I showed an example of how to improve performance of a component with heavy state operations by passing other components to it as children instead of rendering them directly. This article received a question, which sent me into another investigative spiral on how React works, which in turn at some point made me doubt everything that I know about React and even question my own sanity for a short while. Children are not children, parents are not parents, memoization doesn’t work as it should, life is meaningless, re-renders control our life and nothing can stop them (spoiler alert: I emerged victorious from it 😅).

Intrigued I hope? 😉 Let me explain.

## The “children” pattern and a few mysteries

The pattern itself goes like this: imagine you have some frequent state changes in a component. For example, the state is updated in `onMouseMove` callback.

```tsx
const MovingComponent = () => {
  const [state, setState] = useState({ x: 100, y: 100 });

  return (
    <div
      // when the mouse moves inside this component, update the state
      onMouseMove={(e) => setState({ x: e.clientX - 20, y: e.clientY - 20 })}
      // use this state right away - the component will follow mouse movements
      style={{ left: state.x, top: state.y }}
    >
      <ChildComponent />
    </div>
  );
};
```

Now, we know that React components re-render themselves and all their children when the state is updated. In this case, on every mouse move the state of `MovingComponent` is updated, its re-render is triggered, and as a result, `ChildComponent` will re-render as well. If the `ChildComponent` is heavy, its frequent re-renders can cause performance problems for your app.

The way to fight this, other than `React.memo`, is to extract `ChildComponent` outside and pass it as children.

```tsx
const MovingComponent = ({ children }) => {
  const [state, setState] = useState({ x: 100, y: 100 });

  return (
    <div onMouseMove={(e) => setState({ x: e.clientX - 20, y: e.clientY - 20 })} style={{ left: state.x, top: state.y }}>
      // children now will not be re-rendered
      {children}
    </div>
  );
};
```

And compose those two components together like this:

```tsx
const SomeOutsideComponent = () => {
  return (
    <MovingComponent>
      <ChildComponent />
    </MovingComponent>
  );
};
```

The `ChildComponent` “belongs” to the `SomeOutsideComponent` now, which is a parent component of `MovingComponent` and not affected by the state change in it. As a result, it won’t be re-rendered on every mouse move. <a href="https://codesandbox.io/s/childrens-pattern-example-29bi9p">See the codesandbox</a> with both examples.

**Mystery1: but wait, they are still children!**. They are rendered inside a div that changes its style on every mouse move `<div style={{ left: state.x, top: state.y }}>`, i.e. this div is the parent that re-renders. Why exactly children don’t re-render here? 🤔

It gets even more interesting.

**Mystery2: children as a render function**. If I pass children as a render function (a common pattern for cross-components data sharing), `ChildComponent` starts re-rendering itself again, even if it doesn’t depend on the changed state:

```tsx
const MovingComponent = ({ children }) => {
  ...
  return (
    <div ...// callbacks same as before
    >
      // children as render function with some data
      // data doesn't depend on the changed state!
      {children({ data: 'something' })}
    </div>
  );
};

const SomeOutsideComponent = () => {
  return (
    <MovingComponent>
      // ChildComponent re-renders when state in MovingComponent changes!
      // even if it doesn't use the data that is passed from it
      {() => <ChildComponent />}
    </MovingComponent>
  )
}
```

But why? It still “belongs” to the `SomeOutsideComponent` component, and this one doesn’t re-render 🤔 <a href="https://codesandbox.io/s/childrens-pattern-example-with-render-function-dk662n?file=/src/App.tsx">Codesandbox with the example</a>.

**Mystery 3: React.memo behavior**. What if I introduce some state to the outside component `SomeOutsideComponent` and try to prevent re-renders of its children with R`eact.memo`? In the “normal” parent-child relationship just wrapping `MovingComponent` with it is enough, but when `ChildComponent` is passed as children, it still re-renders, even if `MovingComponent` is memoized!

```tsx
// wrapping MovingComponent in memo to prevent it from re-rendering
const MovingComponentMemo = React.memo(MovingComponent);

const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  return (
    <MovingComponentMemo>
      <!-- ChildComponent will still re-render when SomeOutsideComponent re-renders -->
      <ChildComponent />
    </MovingComponentMemo>
  )
}
```

It works though if I memoize just `ChildComponent` without its parent:

```tsx
// wrapping ChildComponent in memo to prevent it from re-rendering
const ChildComponentMemo = React.memo(ChildComponent);

const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  return (
    <MovingComponent>
      <!-- ChildComponent won't re-render, even if the parent is not memoized -->
      <ChildComponentMemo />
    </MovingComponent>
  )
}
```

<a href="https://codesandbox.io/s/childrens-pattern-example-with-react-memo-vx9qqv?file=/src/App.tsx">See codesandbox.</a>

**Mystery4: useCallback hook behavior**. But when I pass `ChildComponent` as a render function, and try to prevent its re-renders by memoizing that function, it just doesn’t work 😬

```tsx
const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  // trying to prevent ChildComponent from re-rendering by memoising render function. Won't work!
  const child = useCallback(() => <ChildComponent />, []);

  return (
    <MovingComponent>
      <!-- Memoized render function. Didn't help with re-renders though -->
      {child}
    </MovingComponent>
  )
}
```

<a href="https://codesandbox.io/s/childrens-pattern-example-with-render-func-and-use-callback-i381k7?file=/src/App.tsx">See codesandbox</a>.

Can you solve those mysteries now, without looking further into the answers? 😉

If you decided you want to know the answers right now, a few key concepts we need to understand first, before jumping into the solutions.

## What exactly are React “children”?

First of all, what exactly are “children”, when they are passed like this?

```tsx
const Parent = ({ children }) => {
  return <>{children}</>;
};

<Parent>
  <Child />
</Parent>;
```

Well, the answer is simple - <a href="https://reactjs.org/docs/composition-vs-inheritance.html#containment">they are just a prop</a>. The fact that we’re accessing them through the rest of the props kinda gives it away 😁

```tsx
const Parent = (props) => {
  return <>{props.children}</>;
};
```

The fancy “composition” pattern that we use is nothing more than a syntax sugar for our convenience. We can even re-write it to be a prop explicitly, it will be exactly the same:

```tsx
<Parent children={<Child />} />
```

And same as any other prop, we can pass components there as Elements, Functions, or Components - this is where the “render function in children” pattern comes from. We can totally do this:

```tsx
// as prop
<Parent children={() => <Child />} />

// "normal" syntax
<Parent>
  {() => <Child />}
</Parent>

// implementation
const Parent = ({ children }) => {
  return <>{children()}</>
}
```

or even this:

```tsx
<Parent children={Child} />;

const Parent = ({ children: Child }) => {
  return <>{<Child />}</>;
};
```

Although the last one probably shouldn’t do, no one on your team will appreciate it.

See this article for more details on those patterns, how they work and the re-renders related caveats: <a href="https://www.developerway.com/posts/react-component-as-prop-the-right-way">React component as prop: the right way™️</a>

In a way, this gives us the answer to the mystery number one, if the answer “components passed as “children” don’t re-render since they are just props” is acceptable.

## What is React Element?

The second important thing to understand is what exactly is happening when I do this:

```tsx
const child = <Child />;
```

Quite often people assume that this is how components are rendered, and this is when the rendering cycle for the `Child` component kicks in. This is not true.

`<Child />` is what is called an “Element”. This is nothing more than syntax sugar again for a function <a href="https://reactjs.org/docs/react-api.html#createelement">React.createElement</a> that returns an object. And this object is just a description of the things you want to see on the screen _when this element actually ends up in the render tree_. Not sooner.

Basically, if I do this:

```tsx
const Parent = () => {
  // will just sit there idly
  const child = <Child />;

  return <div />;
};
```

`child` constant will be just a constant that contains an object that just sits there idly.

You can even replace this syntax sugar with a direct function call:

```tsx
const Parent = () => {
  // exactly the same as <Child />
  const child = React.createElement(Child, null, null);

  return <div />;
};
```

<a href="https://codesandbox.io/s/react-create-element-w1izhe?file=/src/App.tsx">See codesandbox.</a>

**Only** when I actually include it in the return result (which is a synonym for “render those stuff” in functional components), and **only** after `Parent` component renders itself, will the actual render of `Child` component be triggered.

```tsx
const Parent = () => {
  // render of Child will be triggered when Parent re-renders
  // since it's included in the return
  const child = <Child />;

  return <div>{child}</div>;
};
```

## Updating Elements

Elements are immutable objects. The only way to update an Element, and trigger its corresponding component re-render, is to re-create an object itself. This is exactly what is happening during re-renders:

```tsx
const Parent = () => {
  // child definition object will be re-created.
  // so Child component will be re-rendered when Parent re-renders
  const child = <Child />;

  return <div>{child}</div>;
};
```

If the `Parent` component re-renders, the content of the `child` constant will be re-created from scratch, which is fine and super cheap since it’s just an object. `child` is a new Element from React perspective (we re-created the object), but in exactly the same place and exactly the same type, so React will just update the existing component with the new data (re-render the existing `Child`).

And this is what allows memoization to work: if I wrap `Child` in `React.memo`

```tsx
const ChildMemo = React.memo(Child);

const Parent = () => {
  const child = <ChildMemo />;

  return <div>{child}</div>;
};
```

or memoize the result of the function call

```tsx
const Parent = () => {
  const child = useMemo(() => <Child />, []);

  return <div>{child}</div>;
};
```

the definition object will not be re-created, React will think that it doesn’t need updating, and Child’s re-render won’t happen.

React docs give a bit more details on how all of this works if you fancy an even deeper dive: <a href="https://reactjs.org/docs/rendering-elements.html">Rendering Elements</a>, <a href="https://reactjs.org/docs/react-without-jsx.html">React Without JSX</a>, <a href="https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html#elements-describe-the-tree">React Components, Elements, and Instances</a>.

## Resolving the mysteries

Now, that we know all of the above, it’s very easy to resolve all the mysteries that triggered this investigation. Key points to remember:

1. When we’re writing `const child = <Child />`, we’re just creating an `Element`, i.e. component definition, not rendering it. This definition is an immutable object.
2. Component from this definition will be rendered only when it ends up in the actual render tree. For functional components, it’s when you actually return it from the component.
3. Re-creating the definition object will trigger the corresponding component’s re-render

And now to the mysteries' solutions.

**Mystery 1**: why components that are passed as props don’t re-render?

```tsx
const MovingComponent = ({ children }) => {
  // this will trigger re-render
  const [state, setState] = useState();
  return (
    <div
      // ...
      style={{ left: state.x, top: state.y }}
    >
      <!-- those won't re-render because of the state change -->
      {children}
    </div>
  );
};

const SomeOutsideComponent = () => {
  return (
    <MovingComponent>
      <ChildComponent />
    </MovingComponent>
  )
}
```

“children” is a `<ChildComponent />` element that is created in `SomeOutsideComponent`. When `MovingComponent` re-renders because of its state change, its props stay the same. Therefore any `Element` (i.e. definition object) that comes from props won’t be re-created, and therefore re-renders of those components won’t happen.

**Mystery 2**: if children are passed as a render function, they start re-rendering. Why?

```tsx
const MovingComponent = ({ children }) => {
  // this will trigger re-render
  const [state, setState] = useState();
  return (
    <div ///...
    >
      <!-- those will re-render because of the state change -->
      {children()}
    </div>
  );
};

const SomeOutsideComponent = () => {
  return (
    <MovingComponent>
      {() => <ChildComponent />}
    </MovingComponent>
  )
}
```

In this case “children” are a function, and the Element (definition object) is the result of calling this function. We call this function inside `MovingComponent`, i.e. we will call it on every re-render. Therefore on every re-render, we will re-create the definition object `<ChildComponent />`, which as a result will trigger ChildComponent’s re-render.

**Mystery 3**: why wrapping “parent” component in `React.memo` won't prevent the "child" from outside re-render? And why if “child” is wrapped in it, there is no need to wrap the parent?

```tsx
// wrapping MovingComponent in memo to prevent it from re-rendering
const MovingComponentMemo = React.memo(MovingComponent);

const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  return (
    <MovingComponentMemo>
      <!-- ChildComponent will re-render when SomeOutsideComponent re-renders -->
      <ChildComponent />
    </MovingComponentMemo>
  )
}
```

Remember that children are just props? We can re-write the code above to make the flow clearer:

```tsx
const SomeOutsideComponent = () => {
  // ...
  return <MovingComponentMemo children={<ChildComponent />} />;
};
```

We are memoizing only `MovingComponentMemo` here, but it still has children prop, which accepts an Element (i.e. an object). We re-create this object on every re-render, <a href="https://reactjs.org/docs/react-api.html#reactmemo">memoized component</a> will try to do the props check, will detect that children prop changed, and will trigger re-render of `MovingComponentMemo`. And since ChildComponent’s definition was re-created, it will trigger its re-render as well.

And if we do the opposite and just wrap `ChildComponent`:

```tsx
// wrapping ChildComponent in memo to prevent it from re-rendering
const ChildComponentMemo = React.memo(ChildComponent);

const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  return (
    <MovingComponent>
      <!-- ChildComponent won't be re-rendered anymore -->
      <ChildComponentMemo />
    </MovingComponent>
  )
}
```

In this case, `MovingComponent` will still have “children” prop, but it will be memoized, so its value will be preserved between re-renders. `MovingComponent` is not memoized itself, so it will re-render, but when React reaches the “children” part, it will see that definition of `ChildComponentMemo` hasn’t changed, so it will skip this part. Re-render won’t happen.

<a href="https://codesandbox.io/s/childrens-pattern-example-with-react-memo-vx9qqv">See the codesandbox</a>.

**Mystery 4**: when passing children as a function, why memoizing this function doesn’t work?

```tsx
const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  // this memoization doesn't prevent re-renders of ChildComponent
  const child = useCallback(() => <ChildComponent />, []);

  return <MovingComponent>{child}</MovingComponent>;
};
```

Let’s first re-write it with “children” as a prop, to make the flow easier to understand:

```tsx
const SomeOutsideComponent = () => {
  // trigger re-renders here with state
  const [state, setState] = useState();

  // this memoization doesn't prevent re-renders of ChildComponent
  const child = useCallback(() => <ChildComponent />, []);

  return <MovingComponent children={child} />;
};
```

Now, what we have here is: `SomeOutsideComponent` triggers re-render. `MovingComponent` is its child, and it’s not memoized, so it will re-render as well. When it re-renders, it will call the children function during re-render. The function is memoized, yes, but its return is not. So on every call, it will call `<ChildComponent />`, i.e. will create a new definition object, which in turn will trigger re-render of `ChildComponent`.

That flow also means, that if we want to prevent `ChildComponent` from re-renders here, we have two ways to do that. We either need to memoize the function as it is now **AND** wrap `MovingComponent` in `React.memo`: this will prevent `MovingComponent` from re-rendering, which means the “children” function never will be called, and `ChildComponent` definition will never be updated.

**OR**, we can remove function memoization here, and just wrap `ChildComponent` in `React.memo`: `MovingComponent` will re-render, “children” function will be triggered, but its result will be memoized, so `ChildComponent` will never re-render.

And indeed, both of them work, <a href="https://codesandbox.io/s/childrens-pattern-example-with-render-func-and-use-callback-solution-qnmemy?file=/src/App.tsx">see this codesandbox</a>.

That is all for today, hope you enjoyed those little mysteries and will have full control over who renders what next time you write components ✌🏼

...

Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

[Subscribe to the newsletter](https://www.developerway.com), [connect on LinkedIn](https://www.linkedin.com/in/adevnadia/) or [follow on Twitter](https://twitter.com/adevnadia) to get notified as soon as the next article comes out.