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

Viewed 10141

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.

3 Answers

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>

memo will not block re-render if the component is actually referenced the changing props or functions.

In your scenario your AvatarImage referenced img, in this case if parent's state's img is changed, then your component will be re-rendered. Alternatively, if your parent is just changed other props instead of img, then the AvatarImage will NOT be re-rendered.

Alternatively, if any props but you didn't add memo to AvatarImage, then AvatarImage will be re-rendered for each of parent's state updated.

Related