Home
React
React Suspense: Suspense img
June 24, 2025
2 min

Table Of Contents

01
🧨 The Problem: UI Mismatch with Slow Image Loads
02
✅ The Goal: A Smooth Image Transition Experience
03
⚙️ Preloading Images with Promises
04
🧪 Suspending on Images in Practice
05
🛡️ Error Boundaries for Broken Images
06
⚡ Advanced: Fixing Image Transitions with key
07
🧑‍🏫 Summary: Best Practices for Async Images with Suspense
08
🚀 Want Even Smoother UX?

🖼️ 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

Related Posts

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

Social Media

githublinkedinyoutube