How to animate an image in React when it changes


I want an image to do an animation whenever it is changed. I've created the animation but it only happens once upon creation.

Let's say we want to build a simple app that displays an image chosen by the user. Each time a new image appears, we want to show it with an animation.

function ImageAnimation() {
  const [imgId, setImgId] = useState(10);

  return (
    <div>
      <input
        style={{ display: 'block' }}
        value={imgId}
        type="number"
        onChange={(e) => setImgId(e.target.value)}
      />
      <img
        style={{ animation: 'flash 1.5s .5s' }}
        alt="user selected"
        src={`https://i.picsum.photos/id/${imgId}/300/300.jpg`}
      />
    </div>
  );
}

The code above makes a request to Lorem Picsum API to fetch an image. The image shown will depend on value of the input. If the user changes the input value, a new request to the API will be done to fetch the photo with an id equal to the current input value.

Currently, the animation will only work on the first shown image. It won't work as the user changes the image.

Animation not working on change

The animation works for the first the image, because an animation executes when a DOM element enters the DOM. But it won't execute again if the src attribute of the element is changed. This is important since, for our current code, React will only update the src attribute whenever the image changes. It won't unmount and remount the image element.

In vanilla JS, what React is doing is analogous to the following:

imageDomElement.setAttribute(
  'src',
  `https://i.picsum.photos/id/${newImageId}/300/300.jpg`
);

One way to solve this bug, is to tell React that whenever the image is changed, it should be considered a new element. Thus, React shouldn't reuse the existing DOM element and should use a new one instead. What we want to happen, is something similar to what the code below does:

var imageParent = imageDomElement.parentNode;
imageParent.removeChild(imageDomElement);

var newImageElement = document.createElement('img');
newImageElement.setAttribute('style', 'animation: flash 1.5s .5s;');
newImageElement.setAttribute(
  'src',
  `https://i.picsum.photos/id/${newImageId}/300/300.jpg`
);

imageParent.appendChild(newImageElement);

To do this, we can use React's key attribute. If we add a key attribute with the value of the input to the image, and change it whenever the input value is updated, React won't try to reuse the current DOM element. You can read more about the key attribute here.

function ImageAnimation() {
  const [imgId, setImgId] = useState(10);

  return (
    <div>
      <input
        style={{ display: 'block' }}
        value={imgId}
        type="number"
        onChange={(e) => setImgId(e.target.value)}
      />
      <img
        key={imgId}
        style={{ animation: 'flash 1.5s .5s' }}
        alt="user selected"
        src={`https://i.picsum.photos/id/${imgId}/300/300.jpg`}
      />
    </div>
  );
}

This way, the animation will be triggered when the image is changed since it just got added to the DOM.

Animation working on change

Conclusion

The takeaway from this blog post could easily be to remember that an animation will be triggered when a DOM element gets added into the DOM, and that we can make React not reuse the current DOM element by using the key attribute. But I think it runs deeper than that.

I'd say the takeaway from this blog post is that we need to understand how the tools we use work. Without that, we'll be limited in what we can build and the problems we can solve.

Keep learning about how the tools you use work, and why they were designed like that. It will make you a much better software developer.