'React.memo - why is my equality function not being called?

I have a parent component that renders a collection of children based on an array received via props.

import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Content } from 'components-lib';
import Child from '../Child';

const Parent = props => {
  const { items } = props;

  return (
    <Content layout='vflex' padding='s'>
      {items.map(parameter => (
        <Child parameter={parameter} key={shortid.generate()} />
      ))}
    </Content>
  );
};

Parent.propTypes = {
  items: PropTypes.array
};

export default Parent;

Every time a new item is added, all children are re-rendered and I'm trying to avoid that, I don't want other children to be re-rendered I just want to render the last one that was added.

So I tried React.memo on the child where I'll probably compare by the code property or something. The problem is that the equality function never gets called.

import React from 'react';
import PropTypes from 'prop-types';
import { Content } from 'components-lib';

const areEqual = (prevProps, nextProps) => {
  console.log('passed here') // THIS IS NEVER LOGGED!!
}

const Child = props => {
  const { parameter } = props;
  return <Content>{parameter.code}</Content>;
};

Child.propTypes = {
  parameter: PropTypes.object
};

export default React.memo(Child, areEqual);

Any ideas why?



Solution 1:[1]

In short, the reason of this behaviour is due to the way React works.

React expects a unique key for each of the components so it can keep track and know which is which. By using shortid.generate() a new value of the key is created, the reference to the component changes and React thinks that it is a completely new component, which needs rerendering.

In your case, on any change of props in the parent, React will renrender all of the children because the keys are going to be different for all of the children as compared to the previous render.

Please reference this wonderful answer to this topic

Hope this helps!

Solution 2:[2]

I was having the same issue and the solution was a really dumb one. I'm posting this just in case if someone made the same mistake.

If your memo function does have useState variable make sure that you pass it as a prop. Your memo function also has to be outside of your outer function. So instead of:

function App() {
  const [strVar, setStrVar] = useState("My state str");

  const MyElement = React.memo(() => {
    return (
      <Text>
        {strVar}
      </Text>
    )
  }, (prevProps, nextProps) => {
      console.log("Hello"); //Never called
  });

  return (
    <MyElement/>
  )
}

Do it like this:

const MyElement = React.memo(({strVar}) => {
  return (
    <Text>
      {strVar}
    </Text>
  )
}, (prevProps, nextProps) => {
   console.log("Hello");
});


function App() {
  const [strVar, setStrVar] = useState("My state str");

  return (
    <MyElement strVar = {strVar}/>
  )
}

Solution 3:[3]

Another possibility for unexpected renders when including an identifying key property on a child, and using React.memo (not related to this particular question but still, I think, useful to include here).

I think React will only do diffing on the children prop. Aside from this, the children prop is no different to any other property. So for this code, using myList instead of children will result in unexpected renders:

export default props => {
  return (
    <SomeComponent
     myLlist={
      props.something.map(
        item => (
          <SomeItem key={item.id}>
            {item.value}
          </SomeItem>
        )
      )
     }
    />
  )
}

// And then somewhere in the MyComponent source code:
...
{ myList } // Instead of { children }
...

Whereas this code (below), will not:

export default props => {
  return (
    <SomeComponent
     children={
      props.something.map(
        item => (
          <SomeItem key={item.id}>
            {item.value}
          </SomeItem>
        )
      )
     }
    />
  )
}

And that code is exactly the same as specifying the children prop on MyComponent implicitly (except that ES Lint doesn't complain):

export default props => {
  return (
    <SomeComponent>
    {props.something.map(
      item => (
        <SomeItem key={item.id}>
          {item.value}
        </SomeItem>
      )
    )}
    </SomeComponent>
  )
}

Solution 4:[4]

I don't know the rest of your library but I did some changes and your code and (mostly) seems to work. So, maybe, it can help you to narrow down the cause.

https://codesandbox.io/s/cocky-sun-rid8o

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Konstantin
Solution 2
Solution 3 Zach Smith
Solution 4 Afia