Why React.memo doesn't work with `props.children` property?

Viewed 704

I'm trying to wrap a subcomponent ("Box") in React.memo() to avoid the unnecessary re-renders of it when the parent component re-renders from state change. But this didn't work with props.children property. When I tried again without using props.children, it did work! But why? And how can i achieve this using composition and props.children property if not with memo ?

(1) Passing the component example (worked): <Box title="i am a box" />

(2) Passing the component example (didn't work): <Box> <h1>I am a box</h1> </Box>

///

Working example (1):

export default memo (function Box(props) { return ( <div className='box'><h1>{props.title}</h1></div> ) } );

Box Component with inheritance

Parent component: enter image description here

Not working example (2):

export default memo (function Box(props) { return ( <div className='box'>{props.children}</div> ) } );

Box Component with composition

Parent component: enter image description here

2 Answers

Children of the component are actually coming from its parent. When parent re-renders, it recalculates everything and the return statement is triggered. Everything in that return statement is re-created and all the children are asked to re-render.

Some of them might be memoized components, which will decide if they should re-render based on the props. However, when you pass children to that component, for every re-render of the parent, you send a new instance of children to that memoized component. Hence, it causes it to re-render.

I can explain this.

  1. Memo does light comprasion, no deeply comprasion.
  2. When you pass string in children, then memo will be work with children.
  3. If you pass some elements or components, props.childrens will be contain array with refs on passed elements. Each render will receive a new array. An array may have the same content, but the reference to the array will be different. These are different arrays, memo doesn't do deep comparisons. It does not compare the contents of arrays, objects, but only references to them.

Based on the above, why doesn't this work on your example. You are passing the "h1" element as children. In your child component comes an array with one "h1" element. And for each render of the parent, a new array will come. Yes, it will be an array with the same content, but it is not the same array, not the same reference. Memo does not do deep checks.

Below two examples, with string and elements.

// if you pass string as children, memo will be work

const MemoizedChildComponent = memo(({children}) => {
  console.log('render', children) // only one time render, children: "string"
  return <div>children</div>
})

const App = () => {
  const [count, setCount] = useState(1)

  const handleCountIncrement = () => {
    setCount(count + 1)
  }

  return <div>
    <button onClick={handleCountIncrement}>increase</button>
    <MemoizedChildComponent>string</MemoizedChildComponent> // pass simple string
  </div>
}

But what if you don’t want to abandon the elements and want to memoize them?

I suggest use useMemo hook for memoize passed children.

I won't say that's good. But it will help if your goal is to memoize children. You can't solve this problem with memo. There is no deep comparison, so there is a problem with memo and children. Don't forget to specify dependencies in useMemo, there are none in the example, so the dependencies array is empty.

const MemoizedChildComponent = memo(({children}) => {
  console.log('render', children) // only one time render, because it children memoized in parent component inside useMemo hook. children: [object, object, object]
  return <div>children</div>
})

const App = () => {
  const [count, setCount] = useState(1)

  const handleCountIncrement = () => {
    setCount(count + 1)
  }

  const memoizedChildren = useMemo(() => [
    <p key="1">asd</p>,
    <p key="2">asd</p>,
    <p key="3">asd</p>
  ], [])

  return <div>
    <button onClick={handleCountIncrement}>increase</button>
    <MemoizedChildComponent>{memoizedChildren}</MemoizedChildComponent>
  </div>
}
Related