Skip to main content

The React Suspense Composable Streaming Trick For Improved User Experience

 

The React Suspense Composable Streaming Trick For Improved User Experience


The React Suspense Composable Streaming Trick For Improved User Experience

I was building a React app some time ago, and I noticed I had to stare at a blank screen while data loads. That’s not just annoying, it’s a dealbreaker for keeping users engaged.

Today we are going to discuss how to use React’s Suspense to stream data and render UI components smoothly, keeping your app responsive and delightful.Let’s start with a simple example

Here’s a quick code snippet showing Suspense in action for a data-dependent component.

import { Suspense } from "react";
let cache = new Map();
export function fetchData(id) {
if (!cache.has(id)) {
const delay = Math.floor(Math.random() * 2500) + 1000;
const promise = new Promise((resolve) => setTimeout(resolve, delay));
    cache.set(id, promise);
}
return cache.get(id);
}
function Page() {
return (
<div
style={{
border: "2px solid black",
borderRadius: "8px",
display: "flex",
gap: "10px",
alignItems: "center",
justifyContent: "center",
padding: "100px",
margin: "30px",
}}
>
{Array.from({ length: 16 }).map((_, i) => (
<div key={i}>
<Suspense fallback={<span>⏳</span>}>
<SlowCheckmark id={i} />
</Suspense>
</div>
))}
</div>
);
}
async function SlowCheckmark({ id }) {
await fetchData(id);
  return <span>✅</span>;
}
const App = () => (
<div>
<Page />
</div>
);
export default App;

This simple example streams 16 components instances from server to the client. The fallback UI (a simple “Loading…” with a timer emoji) keeps users informed without jarring blank screens.

Why this approach works?

  • Non-blocking rendering: The Suspense allows me to render the the rest of your UI while data is being fetched, avoiding a frozen app.
  • User-friendly feedback: It shows my fallback UI to inform users about the progress, keeping users engaged during waits.
  • Simple integration: The best part is that using it is very simple. I just wrap components in Suspense without rewriting my data-fetching logic. These features make the app feel faster and more polished, even on slow networks.

A cool real world example

Let’s look at a cool example of a blog. Suppose you are building a blog where you want to load comments on demand. Suspense with the streaming technique allows you to do that. This makes your blog faster for users.

import { Suspense } from "react";
import { cms } from "@/cms";
export async function Page() {
const post = await cms.posts.findFirst({
where: { slug: "streaming-with-suspense" },
});
  return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
      <Suspense fallback={<span>Loading comments...</span>}>
<Comments slug="streaming-with-suspense" />
</Suspense>
</div>
);
}
async function Comments({ slug }) {
const comments = await cms.comments.findAll({
where: { slug },
});
  return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>{comment.content}</li>
))}
</ul>
);
}

This simple implementation boosts up the speed and improves user experience a lot. The comment is loaded without blocking the main content of the page.

This keeps users engaged instead of staring at a blank page.

But wait, isn’t this just lazy loading?

Lazy loading delays component code, but Suspense handles data dependencies with finer control. The streaming is enabled when you wrap a component within Suspense. That’s why streaming is a very powerful technique. I can just wrap the component between Suspense and boom, it’s enabled. No code rewrite required.

The main reason behind it’s simplicity is that the Suspense was built to make a component composable.

Streaming + Composition = ❤️

Suppose you are building an AI chatbot where there is a list of AI models that comes from the API. Instead of blocking the entire UI, you can use Suspense to stream the dropdown component which doesn’t block the entire UI while dropdown is loaded on demand.

import { Suspense } from "react";
import { ModelsListbox, ModelsOptions } from "./client-component";
function ChatBox() {
return (
<form>
<textarea defaultValue="Chat with your favorite AI model..." />
      <ModelsListbox>
<Suspense fallback={<span>Loading models...</span>}>
<CurrentUsersOptions />
</Suspense>
</ModelsListbox>
</form>
);
}
async function CurrentUsersOptions() {
const currentUser = await getCurrentUser();
const models = await getModelsForUser(currentUser);
  return <ModelsOptions models={models} />;
}
"use client";
import {
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
} from "@headlessui/react";
const defaultModel = {
id: "gpt-mini",
name: "GPT Mini",
isDisabled: false,
};
export function ModelsListbox({ children }) {
const [selectedModel, setSelectedModel] = useState(defaultModel);
  return (
<Listbox value={selectedModel} onChange={setSelectedModel}>
<ListboxButton>{selectedModel.name}</ListboxButton>
<ListboxOptions>{children}</ListboxOptions>
</Listbox>
);
}
export function ModelsOptions({ models }) {
return (
<>
{models.map((model) => (
<ListboxOption key={model.id} value={model} disabled={model.isDisabled}>
{model.name}
</ListboxOption>
))}
</>
);
}

Here we are splitting the Suspense boundaries to isolate each component’s data fetch. Each Suspense boundary handles its own fallback, making the UI feel snappy and independent. This approach plays nicely with libraries like React Query or SWR, no custom hacks needed.

Rendering and data fetching

I learnt that when I use <Suspense> to stream components with data fetching improves the user experience when I compare the technique with both rendering and fetching data.

In the example I added above, the dropdown component renders immediately, while fetching data for the models. The options are then stream down from the server without blocking any other part of the application.

One edge case I can think of is when user opens the dropdown before options are loaded. When it happens, user see the loading state.

It’s cool that the Suspense allows me to pick which part of the UI should stream and which should render immediately.

Final takeaway

The coolest part of integrating this solution is that it improves user experience and you don’t have to rewrite the application.

Try it out in your application and share your thoughts in the comment.

Thank you. Let’s meet in the next cool hack with React.

Thank you for being a part of the community

Comments

Popular posts from this blog

CSS only Click-handlers You Might not be using, but you should

  You’re building a simple website, a good-looking landing page with a “See More” button. Instinctively, you reach for JavaScript to handle the button click event. But wait — what if I told you that CSS alone could do the job? Yes. CSS is often underestimated, but it can handle click interactions without JavaScript. In this guide, you’ll learn how to create CSS-only click handlers using the :target pseudo-class, and explore scenarios where this approach makes perfect sense. The :target Pseudo-Class CSS offers several pseudo-classes that let you style elements based on different states ( :hover , :focus , :checked ). But there’s one you might not have used before —  :target . The :target pseudo-class applies styles to an element when its ID matches the fragment identifier in the URL (the part after # ). This behavior is commonly seen when clicking an anchor link that jumps to a section on the same page. Here’s a simple example : <a href="#contact">Go to Contact</...

Sharpen Your Front-End Skills: Quick HTML, CSS & React Interview Challenges

  The source of this image is Chat GPT based on writing! Are you preparing for front-end developer interviews and looking for practical, hands-on ways to improve your HTML, CSS, and React skills? Whether you’re a beginner aiming to build confidence or an experienced developer brushing up on UI skills, small, targeted challenges can make a huge difference. In this article, I’ll walk you through some of the best free and low-cost resources that offer real-world front-end tasks — perfect for interview prep, portfolio building, and daily practice. 1. Frontend Mentor frontendmentor.io Frontend Mentor is one of the most popular platforms for hands-on HTML, CSS, and JavaScript challenges. You get beautifully designed templates (in Figma or image formats) and are asked to bring them to life using clean code. The platform offers difficulty levels ranging from newbie to expert, and it’s perfect for practicing responsiveness and semantic HTML. Bonus : You can even filter for React-based ...

6 Essential JavaScript Concepts Every Developer Should Understand

It’s the only language I’ve used where [] == ![] it's true and where you can, typeof null and somehow get 'object' . But despite all its quirks (and there are many), there are a few core concepts that make life with JS not just easier, but saner. This isn’t some computer science flex. These are practical concepts that, once you understand them, make you write better, cleaner, and less buggy code. 1. Hoisting  Before you rage at your variables being undefined , understand this: JS hoists variable and function declarations to the top of their scope. But —  and this is important  —  only the declarations , not the assignments. Why? Because JS reads it like: This is also why let and const behave differently — they’re hoisted too, but live in the “Temporal Dead Zone” until declared. 2. Closures Closures are like little memory vaults for your functions. They allow functions to remember variables from the scope they were created in, even after that scope has gone. Why care? T...