Handling Media in React: Optimizing Images and Loading States
Master media in React by learning to handle loading states, implement smart fallbacks for broken images, and optimize your assets for better performance.
Previously in this course, we explored building a favorites list to manage complex state. This lesson shifts our focus to the visual layer of our movie-browser app: rendering external media.
In a professional application, you cannot assume an image will always load instantly—or even load at all. Network latency, broken API links, and slow connections are facts of life. To build a truly modern UI, you must handle these scenarios gracefully.
Understanding Media from First Principles
In React, the <img> tag is just an HTML element, but when it points to an external source, it behaves like an asynchronous operation. The browser begins downloading the file, and during this time, the component is in a "loading" state.
If the server returns a 404 or the URL is malformed, the browser fires an onError event. If we ignore these events, we end up with "broken" UI—empty white rectangles or ugly browser-default icons that ruin the user experience.
Building a Robust Image Component
Instead of placing raw <img> tags throughout your app, it is a best practice to create a reusable MoviePoster component. This encapsulates the logic for loading states and error fallbacks.
JSXimport { useState } from CE9178">'react'; const MoviePoster = ({ src, alt, title }) => { const [isLoading, setIsLoading] = useState(true); const [hasError, setHasError] = useState(false); // Fallback image if the API returns a broken link const fallbackSrc = CE9178">'/placeholder-movie.png'; return ( <div className="poster-container"> {isLoading && <div className="spinner">Loading...</div>} {!hasError ? ( <img src={src} alt={alt} onLoad={() => setIsLoading(false)} onError={() => { setHasError(true); setIsLoading(false); }} style={{ display: isLoading ? CE9178">'none' : CE9178">'block' }} /> ) : ( <img src={fallbackSrc} alt="Placeholder" /> )} </div> ); };
Key Techniques Explained
- The
onLoadEvent: This fires once the browser has successfully fetched the image. We use it to setisLoadingtofalse, revealing the image only after it's ready. - The
onErrorEvent: This is your safety net. If the image fails to load, we update our state to swap the broken source for a local, reliable placeholder. - Performance Tip: Notice the
style={{ display: isLoading ? 'none' : 'block' }}. This ensures the browser doesn't show a "broken" icon while the image is still being fetched.
Optimizing Media Rendering
Rendering high-resolution images for every device is a performance killer. While advanced image optimization usually happens on the server (using CDNs like Cloudinary or Imgix), you can contribute at the component level:
- Lazy Loading: Add the
loading="lazy"attribute to your<img>tags. This tells the browser to wait until the image is near the viewport before downloading it, significantly improving initial page load times. - Aspect Ratio: Use CSS
aspect-ratioto reserve space for the image before it loads. This prevents "layout shift," where the page content jumps around as images pop into existence.
Hands-on Exercise
Update your MovieCard component in the movie-browser project to use a custom MoviePoster component.
- Create a new file
MoviePoster.jsx. - Move your current
imgtag logic into this component. - Implement the
onLoadandonErrorhandlers as shown above. - Add a CSS class to your
poster-containerthat sets a consistentaspect-ratio: 2/3to keep your movie grid perfectly aligned while images load.
Common Pitfalls
- Infinite Loops with
onError: Never set thesrcto the same URL that failed inside theonErrorhandler. This will cause an infinite loop of error events. Always point to a local, static fallback image. - Ignoring Accessibility: Always provide a descriptive
alttag. If the image is decorative, usealt="". If it's a movie poster, use the movie title. - Over-fetching: Don't render full-resolution 4K posters in a small search results list. Ensure your API endpoint provides a "thumbnail" or "poster_small" version of the URL.
Recap
Handling media in React is about anticipating the unpredictable nature of the network. By managing images with dedicated state for loading and errors, you create a resilient interface. Remember to use loading="lazy" to keep your app performant and ensure you provide fallbacks to prevent empty UI blocks.
Up next: We will begin our unit testing journey, starting with an introduction to testing our React components.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds — familiar editing, modern speed.