When working on modern web applications, performance is often a key concern, and images are a frequent culprit in slowing things down. Lazy loading images is one effective strategy to improve your app’s performance by only loading images as they are needed. However, handling situations where an image fails to load is just as important to maintain a polished user experience. In this post, I’ll walk you through creating a React component using TypeScript that lazy loads images and provides a fallback image if the original image fails to load.
Thank me by sharing on Twitter 🙏
Why Lazy Loading Matters
Before jumping into the implementation, let me highlight why lazy loading is such a powerful technique. By loading images only when they come into view, lazy loading reduces the initial load time of your application, leading to faster rendering and a better user experience. It also saves bandwidth by not downloading unnecessary assets, which is especially beneficial for users on slower connections.
To complement lazy loading, implementing a fallback for failed image loads ensures that your application remains visually appealing and functional, even when the intended image is unavailable. Combining these two techniques provides a robust solution for handling images in your app.
Setting Up the Component
To build this component, I used React’s functional components and hooks. I’ll write the implementation in TypeScript to take advantage of type safety and better maintainability.
Here’s the component step-by-step:
Anker USB C to USB C Cable, Type C 60W Fast Charging Cable (6FT, 2Pack) for iPhone 16 Series, iPad Mini 6 and More (USB 2.0, Black)
$7.99 (as of February 25, 2025 13:13 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Co-Intelligence: Living and Working with AI
$17.79 (as of February 25, 2025 13:13 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Logitech M185 Wireless Mouse, 2.4GHz with USB Mini Receiver, 12-Month Battery Life, 1000 DPI Optical Tracking, Ambidextrous PC/Mac/Laptop - Swift Grey
$13.95 (as of February 25, 2025 13:13 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Step 1: Define Props and State
First, I defined the props and state for the component. The component accepts src
, alt
, and defaultImage
as props. These correspond to the main image URL, the alt text for accessibility, and the fallback image URL, respectively.
import React, { useState, useEffect } from 'react';
type LazyImageProps = {
src: string;
alt?: string;
defaultImage: string;
className?: string;
};
The className
prop is optional and allows the user to style the component externally. Using TypeScript ensures that we provide clear expectations for these props.
Step 2: Initialize State and Effects
Next, I initialized state variables to manage the current image source, loading status, and error state. The useEffect
hook dynamically updates these values based on the src
prop.
const LazyImage: React.FC<LazyImageProps> = ({ src, alt, defaultImage, className }) => {
const [imageSrc, setImageSrc] = useState<string>("");
const [loaded, setLoaded] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setImageSrc(src);
setLoaded(true);
setHasError(false);
};
img.onerror = () => {
setImageSrc(defaultImage);
setLoaded(true);
setHasError(true);
};
}, [src, defaultImage]);
return (
<div className={className} style={{ position: 'relative' }}>
{!loaded && <p>Loading...</p>}
<img
src={imageSrc}
alt={alt || "Image"}
style={{ opacity: loaded ? 1 : 0, transition: 'opacity 0.3s ease' }}
/>
{hasError && <p style={{ color: 'red' }}>Image failed to load</p>}
</div>
);
};
export default LazyImage;
The useEffect
hook ensures the component reacts dynamically to prop changes. The onload
and onerror
callbacks update the state when the image successfully loads or encounters an error, respectively.
Step 3: Add Styling and Transitions
I included a smooth fade-in effect for the images using the opacity
style property. This enhances the user experience by visually signaling that the image is loaded. Adjusting the transition
duration can provide a custom feel for your application. This styling approach makes it easy to integrate the component into any design system.
Usage Example
To use the LazyImage
component, simply import it and provide the required props. Here’s an example:
import React from 'react';
import LazyImage from './LazyImage';
const App: React.FC = () => {
return (
<div>
<h1>Lazy Loaded Images</h1>
<LazyImage
src="https://example.com/high-res-image.jpg"
defaultImage="/fallback.jpg"
alt="A descriptive alt text"
className="image-style"
/>
</div>
);
};
export default App;
This implementation ensures that your app remains performant and visually consistent, even when dealing with unreliable image sources.
Extending Functionality
While this component covers the basics of lazy loading with error fallback, there are several ways to enhance it further:
- Placeholder Loading Indicators: Replace the simple “Loading…” text with a spinner or skeleton screen for a more polished look.
- Intersection Observer: Use the
IntersectionObserver
API to improve lazy loading by only loading images as they enter the viewport. - Customizable Styles: Allow users to pass inline styles or additional class names to tailor the component’s appearance.
- Accessibility Improvements: Add ARIA attributes or better handle cases where both the primary and fallback images fail.
Wrapping Up
Creating a React component for lazy loading images with a fallback option is a practical way to improve both performance and user experience. By carefully managing state and leveraging the power of React hooks, you can handle common challenges like failed image loads gracefully. This approach ensures your app not only performs well but also looks great under all circumstances.