Why did I need a fallback?
Recently at work, I had to display lots of user data with images on the website I was building. I was getting all the data from an API and it was just a matter of putting things on the screen...
Except that, in some cases, the image for the user didn't exist anymore. So although I had a src for my image tag, there was no image and the page would just show the alternative text I provided. Here you can see a broken src on the left and a normal image on the right:
This looked horrible, so I was asked to put a fallback image whenever there was a problem with the source.
Fallback images in React
For React, the solution is only one additional line to the code you would normally write. Let's take a look:
import fallback from "../public/fallback-image.png";
function ImageWithFallback({ src, alt, fallBackSrc = fallback.src }) {
return (
<div style={{ border: "1px solid black", height: "50vh" }}>
<img
src={src}
alt={alt}
style={{ height: "100%", aspectRatio: "1 / 1", objectFit: "cover" }}
onError={(e) => (e.currentTarget.src = fallBackSrc)}
/>
</div>
);
}
export default ImageWithFallback;
Div and styling are only there for illustration purposes. We can see that this doesn't differ from the regular image tag we already know. The magic happens in the onError callback function, which gets fired as soon as there is a problem with the src. When this happens, our src will be replaced by our fallback image and we can go take a break. ☕
Fallback images with optimized Images in Next.js
In my case, I was using the Image-tag from Next.js to take advantage of lazy loading and image optimization. When I tried to use the same onError function with Next.js, the fallback image would never show! Therefore, I created a piece of state so I could rerender the component in case of an error:
import fallback from "../public/fallback-image.png";
import Image from "next/image";
import { useState } from "react";
function OptimizedImageWithFallback({ src, alt, fallBackSrc = fallback.src }) {
const [imageError, setImageError] = useState(false);
return (
<div
style={{
border: "1px solid black",
position: "relative",
}}
>
<Image
src={imageError ? fallBackSrc : src }
alt={alt}
width={500}
height={500}
objectFit='cover'
onError={() => setImageError(true)}
/>
</div>
);
}
export default OptimizedImageWithFallback;
The outer div is a requirement of the next image tag and again for some styling. The onError function in this case just changes the error state to true, causing a rerender and changing the src to fallBackSrc.
That's it! I hope it helped you out! See you next time 😀
Top comments (5)
do you think this will also solve the image indexing issue that is faced by many.
I have yet to see a Next/Image generated URL to be indexed by google, like this:
www.example.com/_next/image?url=%2Fimages%2Fhome%2FDog-image-1.jpg&w=384&q=100
found conditional logic issue, it should be
src={imageError ? fallBackSrc : src}
....
That's absolutely correct! Thank you for pointing this out - I already the example in the article 😀
This is for one Image component, how can i handle this when i'm listing products with map method,
In the current situation, OptimizedImageWithFallback is thought of as a standalone reusable component. Normally, you could simply iterate through the list using a map and for each element there will be rendered the component.
Example:
items.map(item => <div className="product" />{...other code here}<OptimizedImageWithFallback src={item.src} alt={item.alt} />){...other code here}</div>
(sorry for possible syntax errors, written on the phone)