how to use React.memo with a Component contains children

Viewed 14768

I have two Components, and I wrapped Parent with React.memo:

Child

const Child = ()=> <div>I'm Child</div>

export default Child

Parent

const Parent = (props)=> <div>{props.children}</div>

export default React.memo(Parent)

Use in App:

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

  return(
    <div>
      <button onClick={()=>setCount(count+1)}></button>

      <Parent>
        <Child></Child>
      </Parent>
    </div>
  )
}

Click the button, result:

The Parent Component will rerender, so the memo not working because it's children is a function component

So, how can I prevent rerender?

I know a way to solve by useMemo, but it's ugly and not friendly, do you have better ideas?

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

  const children = useMemo(()=><Child></Child>,[])

  return(
    <div>
      <button onClick={()=>setCount(count+1)}></button>

      <Parent>
        {children}
      </Parent>
    </div>
  )
}
4 Answers

Wrap your <Child /> with React.memo:

const Child = ()=> {
  console.log('render') // fires only once - on initial render
  return <div>I'm Child</div>
}

const MChild = React.memo(Child);
const Parent = (props)=> <div>{props.children}</div>
const MParent = React.memo(Parent)

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

  return(
    <div>
      <button onClick={()=>setCount(count+1)}>increment {count}</button>
      <MParent>
        <MChild></MChild>
      </MParent>
    </div>
  )
}

render(<App />, document.getElementById('root'));
const children = useMemo(()=><Child></Child>,[])

Is the easiest way to go. Using memo(Child) wont work since jsx in fact returns a new object whenever you call <Child />. React.memo by default just use simple shallow comparison so there really is no other direct way to solve it. You can create your own function that would eventually support children and pass it to React.memo(MyComp, myChildrenAwareEqual).

Move <Parent><Child/></Parent> into a separate component, and memoize that component instead:

const Family = memo(() => <Parent><Child/></Parent>);

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

  return (
    <>
      <button onClick={() => setCount(count => count + 1)}>{count}</button>
      <Family />
    </>
  )
}

demo

const Memo = React.memo(({ children, value }) => {
  return children
}, (prev, next) => prev.value === next.value)

And then to use it

function Title() {
  const [count, setCount] = useState(0)
  const onClick = () => {
    setCount(c => c + 1)
  }

  const a = ''

  return (
    <>
      <div onClick={onClick}>{count}</div>
      <Memo value={a}>
        <Child>
          <Nothing2 a={a} />
        </Child>
      </Memo>
    </>
  )
}

When a changes, Child renders, otherwise it bails out with previous render.

I outline the rational behind it in my blog, https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981

Related