React Concurrent Features & Suspense
Deep dive into React 18+ concurrent features: Suspense, useDeferredValue, useTransition, streaming SSR, and how to build responsive UIs that never block the user experience.
โณ 1. Suspense & Error Boundaries
Loading spinners everywhere? Not anymore! ๐ Suspense lets you declaratively handle loading states like a boss, and when you pair it with Error Boundaries, your async data loading becomes bulletproof. It's like having a safety net for your entire component tree!
๐ด Impact: CRITICAL โ Suspense is THE way React wants you to handle async operations โ this is non-negotiable knowledge! ๐ฏ
๐ In this section: Suspense Boundaries โข Error Fallbacks โข Async Data Loading โข Skeleton UIs
import { Suspense } from 'react';const MOCK_USER = { name: "Demo User", email: "demo@example.com" };function fetchUser() {const p = new Promise((r) => setTimeout(() => r(MOCK_USER), 600));(p as any).status = undefined;return p;}function use<T>(promise: Promise<T>): T {const p = promise as any;if (p.status === 'fulfilled') return p.value;if (p.status === 'rejected') throw p.reason;if (p.status === 'pending') throw promise;p.status = 'pending';promise.then((result) => { p.status = 'fulfilled'; p.value = result; },(reason) => { p.status = 'rejected'; p.reason = reason; });throw promise;}function UserProfile({ userId }: { userId: string }) {const user = use(fetchUser());return (<div><h1>{user.name}</h1><p>{user.email}</p></div>);}function ErrorFallback() { return <div style={{ color: '#f87171' }}>Error</div>; }function UserProfileSkeleton() { return <div>Loading user...</div>; }function App() {return (<Suspense fallback={<UserProfileSkeleton />}><UserProfile userId="123" /></Suspense>);}export default App;
๐ 2. useDeferredValue Pattern
Ever typed in a search box and the whole UI froze? ๐ฅถ useDeferredValue is your secret weapon! It tells React "hey, this update can wait" so your input stays buttery smooth while heavy filtering happens in the background. Your users will love you for it!
๐ Impact: HIGH โ Laggy search inputs are the fastest way to lose users โ this hook makes that problem disappear! โจ
๐ In this section: Deferred State Updates โข Search Optimization โข Pending UI States โข Before/After Comparison
// โ WRONG: Blocks UI during filteringfunction SearchList({ items }: { items: string[] }) {const [query, setQuery] = useState('');const filtered = items.filter(item =>item.toLowerCase().includes(query.toLowerCase()));return (<div><inputvalue={query}onChange={e => setQuery(e.target.value)}placeholder="Search..."/>{/* Input feels laggy with 10,000 items */}<ul>{filtered.map(item => <li key={item}>{item}</li>)}</ul></div>);}
// โ CORRECT: Deferred filtering keeps input responsiveimport { useState, useDeferredValue, useMemo } from 'react';function SearchList({ items }: { items: string[] }) {const [query, setQuery] = useState('');const deferredQuery = useDeferredValue(query);const filtered = useMemo(() => {return items.filter(item =>item.toLowerCase().includes(deferredQuery.toLowerCase()));}, [items, deferredQuery]);const isPending = query !== deferredQuery;return (<div><inputvalue={query}onChange={e => setQuery(e.target.value)}placeholder="Search..."style={{ opacity: isPending ? 0.5 : 1 }}/>{/* Input stays responsive, filtering happens in background */}{isPending && <div>Searching...</div>}<ul>{filtered.map(item => <li key={item}>{item}</li>)}</ul></div>);}
๐ญ 3. useTransition for Non-Blocking UI
Think of useTransition as a VIP lane for your state updates! ๐๏ธ Urgent stuff (typing, clicking) gets the fast lane, while heavy operations (filtering 10K items, switching tabs) cruise in the background. The result? A UI that NEVER. FEELS. SLOW.
๐ Impact: HIGH โ useTransition is the difference between a janky app and a silky-smooth experience. Your users can feel it! ๐ง
๐ In this section: startTransition API โข Tab Switching Pattern โข List Filtering โข isPending States
// โ Tab switching with transitionsimport { useState, useTransition } from 'react';function TabContainer({ tabs }: { tabs: Tab[] }) {const [activeTab, setActiveTab] = useState(tabs[0].id);const [isPending, startTransition] = useTransition();const handleTabChange = (tabId: string) => {// Urgent: Update active tab immediatelysetActiveTab(tabId);// Non-urgent: Heavy content loading happens in transitionstartTransition(() => {// This won't block the tab switchloadTabContent(tabId);});};return (<div><div className="tabs">{tabs.map(tab => (<buttonkey={tab.id}onClick={() => handleTabChange(tab.id)}className={activeTab === tab.id ? 'active' : ''}disabled={isPending}>{tab.label}</button>))}</div>{isPending && <div>Loading...</div>}<div className="tab-content">{tabs.find(t => t.id === activeTab)?.content}</div></div>);}// โ List filtering with transitionfunction FilterableList({ items }: { items: Item[] }) {const [filter, setFilter] = useState('');const [isPending, startTransition] = useTransition();const handleFilterChange = (value: string) => {// Urgent: Update input immediatelysetFilter(value);// Non-urgent: Filtering happens in backgroundstartTransition(() => {// Heavy filtering won't block input});};const filtered = useMemo(() => {return items.filter(item =>item.name.toLowerCase().includes(filter.toLowerCase()));}, [items, filter]);return (<div><inputvalue={filter}onChange={e => handleFilterChange(e.target.value)}placeholder="Filter..."/>{isPending && <span>Filtering...</span>}<ul>{filtered.map(item => (<li key={item.id}>{item.name}</li>))}</ul></div>);}
๐ 4. Streaming SSR
Why make users stare at a blank screen? ๐ค Streaming SSR sends HTML to the browser as it's ready โ like a buffet where dishes arrive as they're cooked instead of making everyone wait for the full meal. Progressive loading FTW! ๐ฝ๏ธ
๐ด Impact: CRITICAL โ Streaming SSR can cut your Time-to-First-Byte dramatically โ this is how modern Next.js apps fly! ๐ซ
๐ In this section: Progressive HTML Streaming โข Nested Suspense โข Server Components โข Progressive Enhancement
// โ Next.js 13+ App Router with Streaming// app/page.tsximport { Suspense } from 'react';export default function Page() {return (<div><Header />{/* This streams immediately */}<Suspense fallback={<ContentSkeleton />}><FastContent /></Suspense>{/* This streams after FastContent */}<Suspense fallback={<SlowContentSkeleton />}><SlowContent /></Suspense></div>);}// โ Server Component with async dataasync function SlowContent() {// This runs on the serverconst data = await fetchSlowData(); // Takes 2 secondsreturn <div>{data.content}</div>;}// โ React Server Components pattern// Server Componentasync function UserProfile({ userId }: { userId: string }) {const user = await fetchUser(userId);return (<div><h1>{user.name}</h1><UserPosts userId={userId} /></div>);}// Nested Server Componentasync function UserPosts({ userId }: { userId: string }) {const posts = await fetchUserPosts(userId);return (<ul>{posts.map(post => (<li key={post.id}>{post.title}</li>))}</ul>);}// โ Progressive Enhancementfunction ProgressivePage() {return (<div>{/* Critical content loads first */}<CriticalContent />{/* Non-critical content streams in */}<Suspense fallback={null}><NonCriticalContent /></Suspense></div>);}