HomeAbout Me

React Suspense: Suspense img

By Daniel Nguyen
Published in React JS
June 24, 2025
2 min read
React Suspense: Suspense img

🖼️ Smooth & Reliable Image Loading with React Suspense

React Suspense has revolutionized how we handle asynchronous behavior in UI—especially when fetching data. But did you know you can also suspend image loading to create smoother and more predictable visual transitions?

This guide will walk you through:

  • Why suspending images is useful
  • How to preload images for Suspense
  • Handling image load errors gracefully
  • Avoiding flickers using suspense key props

Let’s start by understanding the problem.


🧨 The Problem: UI Mismatch with Slow Image Loads

Here’s a scenario:

  1. A user clicks to view a different pokemon.
  2. The new data loads in ~2 seconds.
  3. The new image loads in ~10 seconds.
  4. The old image is still shown while new data is already visible.

This results in a confusing user experience: the content changes, but the image doesn’t update right away.

🎥 Watch the problem — the pokemon name and stats change, but the image lags far behind.


✅ The Goal: A Smooth Image Transition Experience

We want:

  • ⏱️ Pokemon data to show as soon as it’s ready
  • 📷 Pokemon image to show as soon as it’s loaded
  • ❌ No flickering or showing outdated images

React Suspense gives us the tools to accomplish exactly that.


⚙️ Preloading Images with Promises

React Suspense lets us suspend rendering on any async operation—not just data fetching.

So how do we suspend for images?

By preloading them manually:

function preloadImage(src: string) {
return new Promise<string>((resolve, reject) => {
const img = new Image()
img.src = src
img.onload = () => resolve(src)
img.onerror = reject
})
}

Now you can wrap this in a cache to avoid reloading the same image:

const imgCache = new Map<string, Promise<string>>()
function getImgSrc(src: string) {
if (!imgCache.has(src)) {
imgCache.set(src, preloadImage(src))
}
return imgCache.get(src)!
}

Then use React’s use() hook inside your custom <Img> component:

function Img({ src, alt }: { src: string; alt: string }) {
const loadedSrc = use(getImgSrc(src))
return <img src={loadedSrc} alt={alt} />
}

🧪 Suspending on Images in Practice

By default, React suspends the whole subtree when something inside it suspends. So to suspend only the image, wrap your <Img> in a Suspense boundary:

<Suspense fallback={<FallbackImage />}>
<Img src={pokemon.imageUrl} alt={pokemon.name} />
</Suspense>

That’s great… but what happens when the image fails to load?


🛡️ Error Boundaries for Broken Images

If an image fails to load (e.g. due to a bad URL or offline connection), React will throw inside use(). If that happens inside Img, we want to show a fallback image—not crash the entire pokemon component!

Here’s how to make an image-specific error boundary:

function PokemonImg({ src, alt }: { src: string; alt: string }) {
return (
<ErrorBoundary fallback={<img src={src} alt={alt} />}>
<Suspense fallback={<FallbackImage />}>
<Img src={src} alt={alt} />
</Suspense>
</ErrorBoundary>
)
}

This ensures:

  • ✅ Fallback image during load
  • ❌ Error fallback if loading fails
  • 🧠 Clear user feedback instead of a broken UI

⚡ Advanced: Fixing Image Transitions with key

You might still run into a UX problem:

The UI waits for both data and image to load before updating the screen. That’s not ideal.

Why? Because Suspense boundaries inside a transition (useTransition) won’t show their fallback. They’ll keep the old UI until everything is ready.

🧩 The Solution: Add a Unique key to the Suspense Boundary

By giving your Suspense (or ErrorBoundary) a dynamic key, you tell React:

“This is a brand-new boundary—treat it like an initial render.”

React will then show the fallback for just that Suspense boundary, even while other parts of the UI transition smoothly.

function PokemonImg({ src, alt }: { src: string; alt: string }) {
return (
<ErrorBoundary fallback={<img src={src} alt={alt} />} key={src}>
<Suspense fallback={<FallbackImage />}>
<Img src={src} alt={alt} />
</Suspense>
</ErrorBoundary>
)
}

✅ This shows pokemon data ASAP ✅ Shows image only when it’s loaded ✅ Prevents showing old images with new content

🎥 See the improved experience


🧑‍🏫 Summary: Best Practices for Async Images with Suspense

FeatureTechnique
Preload imagenew Image() with onload/onerror
Suspend on loadWrap preloadImage() in a cache and use use()
Fallback UIWrap <Img> with <Suspense fallback={...}>
Error fallbackWrap Suspense with <ErrorBoundary>
Fix transition behaviorAdd key={src} to Suspense/ErrorBoundary

🚀 Want Even Smoother UX?

Combine this with:

  • useTransition for smooth UI while transitioning content
  • spin-delay to delay spinners for brief loads
  • useOptimistic to show UI even before the async task resolves


Tags

#ReactSuspense

Share

Previous Article
React Suspense: Optimistic UI

Table Of Contents

1
🧨 The Problem: UI Mismatch with Slow Image Loads
2
✅ The Goal: A Smooth Image Transition Experience
3
⚙️ Preloading Images with Promises
4
🧪 Suspending on Images in Practice
5
🛡️ Error Boundaries for Broken Images
6
⚡ Advanced: Fixing Image Transitions with key
7
🧑‍🏫 Summary: Best Practices for Async Images with Suspense
8
🚀 Want Even Smoother UX?

Related Posts

React Suspense: Optimizations
June 26, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media