23

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>
  )
}
Vukašin Manojlović
  • 3,717
  • 3
  • 19
  • 31
Yokiijay
  • 711
  • 2
  • 7
  • 18
  • The `Child` component is rendered by the `App` component. So either you move the rendering of `Child` into the `Parent` component or you also wrap `Child` in `React.memo()`. – trixn Mar 13 '20 at 11:24
  • no, it wont work, I tried... – Yokiijay Mar 13 '20 at 11:28
  • 1
    Okay so your `Parent` will still re-render because its `children` prop technically changes when `App` re-renders, which is why even `React.memo()` doesn't help there. Basically the best way around that seems to be what you did using `useMemo`. You have to memorize what you pass to `Parent` as children or it will re-render. – trixn Mar 13 '20 at 11:33
  • Thank you, there is another way React.memo(Parent, ()=>true). But it's unsafe... – Yokiijay Mar 13 '20 at 11:52
  • What if you have a family tree? I mean, `` and so on, it goes deep many layers (my app is a bunch of mini components). How do I deal with this complexity? – Greeso Feb 04 '23 at 02:55

4 Answers4

11
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).

Maxime Bret
  • 111
  • 1
  • 2
10

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'));
Remek Ambroziak
  • 790
  • 8
  • 11
  • 3
    But what happens if you log inside Parent? – Stian Jensen Apr 20 '21 at 21:39
  • 1
    For some reason, i don't think this would work. The `children` of `MParent` still changes. I would think a `useMemo` to cache the entire `` can work, like in Maxime's answer. – windmaomao Sep 15 '21 at 00:47
  • 1
    The parent will always re-render when has `children `as prop, so if you have any functions or objects in parent, just use `useMemo` and `useCallback` – shadi Mar 21 '22 at 22:51
  • 1
    You may have misread the question. Yes `MChild` does not re-render because it has no children. But `MParent` re-renders every time despite being memoized, because its `children` prop breaks memoization. See https://codepen.io/tamlyn/pen/ExEBPKd – Tamlyn Aug 27 '22 at 09:01
5

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

Alf Eaton
  • 5,226
  • 4
  • 45
  • 50
  • I think this should be the correct answer. This helped me fix a common list case where `const MyComp = memo((item) => );` and `const renderItem = useCallback((item) => )` and `` – kadrian Aug 03 '22 at 21:30
2
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

windmaomao
  • 7,120
  • 2
  • 32
  • 36
  • Great, simple explanation, thank you! – Chris Livdahl Oct 17 '22 at 23:17
  • I use this tip every time, it saves a lot of bad rendering process. I am wondering if this is a good practice? – Thomas Decaux Oct 19 '22 at 12:48
  • @ThomasDecaux, it's not popular approach if that's what you mean. React team designed the system with the mindset that they don't want us to over-think the engine, thus when it comes to the optimization, you just have to roll your own version. The pattern can be seen in quite a few libraries (including React itself), i believe React.memo even supports a third argument to make it more flexible. But in consumer code, you hardly see this pattern because you can just manually write these lines without the helper. – windmaomao Oct 20 '22 at 19:12
  • some guys told me we should never memo components, this means a wrong architecture. And they recommend me to use proxy pattern or lib such as zustand. – Thomas Decaux Oct 20 '22 at 20:05
  • 1
    @ThomasDecaux, they are funny. If you ever checkout the source code of these library, ex. proxy or zustand, you'll see full of `useMemo` and `useCallback` used ;) I bet each library has at least five time usages of them. You can check yourself if you don't believe me. – windmaomao Oct 21 '22 at 21:22