'React.memo isn't working - what am I missing?

I'm in the process of refactoring some of our components so I'm trying to incorporate memoization as some components may re-render with the same values (for example, hotlinked image URLs unless they are the same).

I have a simple component:

const CardHeader = props => {
    // img is a stringand showAvatar is a boolean but it's always true
    const { ..., showAvatar, img } = props;

    return (
        <CardHeader>
            <ListItem>
                // AvatarImage shouldn't re-render if img is the same as previous
                {showAvatar && <AvatarImage img={img} />
            </ListItem>
        </CardHeader>
    );
}

And then the AvatarImage:

const AvatarImage = React.memo(props => {
   console.log("why is this still re-rendering when the img value hasn't changed?");
   const { img } = props;

   return (
        <ListItemAvatar>
            {img ?
                <Avatar src={img} />    
                :
                <Avatar>
                    Some initials
                </Avatar>
            }
        </ListItemAvatar>
    );
});

I have also tried passing in second argument of memo:

(prevProps, nextProps) => {
    return true; // Don't re-render!
}

But the console.log still shows every time. I'm obviously missing something here or don't quite understand how this works. This component is a few levels down, but it passes in the img if it's available every time so I'd expect it to know that if the img was passed in the previous render and it's the same it knows not to re-render it again but for some reason it does?

Thanks all. It's much appreciated.



Solution 1:[1]

Well it is either showAvatar is not always true or CardHeader ListItem component magically decides whether show children or not

Example

const { useState, useEffect, memo, createContext, useContext } = React;

const getAvatars = () => Promise.resolve([
{
  src: 'https://i.picsum.photos/id/614/50/50.jpg'
},
{
  src: 'https://i.picsum.photos/id/613/50/50.jpg'
}
])

const Avatar = ({src}) => {
console.log('avatar render');
  return <img src={src} alt="avatar"/>
}

const MemoAvatarToggle = memo(({src}) => {
console.log('memo avatar with \'expression &&\' render');
  return <div>
  {src ? <img src={src} alt="avatar"/> : <div>Test </div>}
  </div>
})

const CardHeader = ({children}) => {
  const luck = Boolean(Math.floor(Math.random() * 1.7));
  
  
  
  return <div>
    {luck && children}
  </div>
}

const ListItem = ({children}) => {
  return <div>
    {children}
  </div>
}

const ShowAvatarContext = createContext()

const App = (props) => {
  const [avatars, setAvatars] = useState([]);
  const [toggle, setToggle] = useState(false);
  const [showAvatar, setShowAvatar] = useContext(ShowAvatarContext);
  
  useEffect(() => {
    let isUnmounted = false;
    let handle = null;
    
    setTimeout(() => {
      if(isUnmounted) {
        return;
      }
      setShowAvatar(true);
    }, 500);
    
    getAvatars()
      .then(avatars => {
        if(isUnmounted) {
          return;
        }
        
        setAvatars(avatars)
      })
    
    const toggle = () => {
      setToggle(prev => !prev);
      handle = setTimeout(toggle, 1000);
      //setShowAvatar(prev => !prev);
    }
    
    handle = setTimeout(toggle, 1000);
    
    return () => {
      isUnmounted = true;
      clearTimeout(handle);
    }
      
  }, []);
 
  return <div>
    <CardHeader>
      <ListItem>
        {showAvatar && avatars.map((avatar, index) => <MemoAvatarToggle key={index} src={avatar.src}/>)}
      </ListItem>
    </CardHeader>
    {toggle ? 1 : 0} 
  </div>
}

const ShowAvatarProvider = ({children}) => {
  const state = useState(false);
  
  return <ShowAvatarContext.Provider value={state}>
      {children}
    </ShowAvatarContext.Provider>
}

ReactDOM.render(
    <ShowAvatarProvider>
        <App/>
    </ShowAvatarProvider>,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>

Solution 2:[2]

Do you have StrictMode enabled? That will cause a component memoized with React.memo to render twice.

More information:

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 Józef Podlecki
Solution 2 kentr