Building a React Component for Lazy Loading Images with Error Fallback

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:

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.

TypeScript
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.

TypeScript
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:

TypeScript
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:

  1. Placeholder Loading Indicators: Replace the simple “Loading…” text with a spinner or skeleton screen for a more polished look.
  2. Intersection Observer: Use the IntersectionObserver API to improve lazy loading by only loading images as they enter the viewport.
  3. Customizable Styles: Allow users to pass inline styles or additional class names to tailor the component’s appearance.
  4. 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.

Share this:

Leave a Reply