import { Suspense } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useParams } from 'react-router-dom'
import api from '@/services/api'
import { Post } from '@/types'
import { ErrorBoundary } from '@/components/ErrorBoundary'
function PostContent() {
const { id } = useParams()
// Suspense-enabled query - throws promise if data not ready
const { data: post } = useSuspenseQuery({
queryKey: ['posts', id],
queryFn: async () => {
const { data } = await api.get<Post>(`/posts/${id}`)
return data
},
})
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.body }} />
</article>
)
}
function PostComments({ postId }: { postId: string }) {
const { data: comments } = useSuspenseQuery({
queryKey: ['posts', postId, 'comments'],
queryFn: async () => {
const { data } = await api.get(`/posts/${postId}/comments`)
return data
},
})
return (
<div className="mt-8">
<h2>Comments</h2>
{comments.map((comment: any) => (
<div key={comment.id}>{comment.body}</div>
))}
</div>
)
}
export default function PostDetail() {
const { id } = useParams()
return (
<ErrorBoundary>
<Suspense fallback={<PostSkeleton />}>
<PostContent />
{/* Nested suspense for independent loading */}
<Suspense fallback={<div>Loading comments...</div>}>
<PostComments postId={id!} />
</Suspense>
</Suspense>
</ErrorBoundary>
)
}
function PostSkeleton() {
return <div className="animate-pulse">Loading post...</div>
}
React Suspense handles async operations declaratively, showing fallback UI while data loads. With frameworks like Relay or React Query's experimental Suspense mode, components throw promises when data isn't ready, triggering the nearest Suspense boundary. This pattern eliminates loading state management within components—Suspense boundaries handle it centrally. Multiple components under one boundary show a single loader, while nested boundaries provide granular loading states. Error boundaries catch failures, separating error and loading concerns. Suspense works with lazy-loaded components and code splitting out of the box. Future React features like concurrent rendering leverage Suspense for smoother UIs. It fundamentally changes how we think about async data in React.
Related snips
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Rails.application.configure do
config.after_initialize do
Bullet.enable = true
Bullet.alert = false
Bullet.bullet_logger = true
Bullet.console = true
N+1 query detection with Bullet gem
json.array! @posts do |post|
json.cache! ['v1', post], expires_in: 1.hour do
json.id post.id
json.title post.title
json.excerpt post.excerpt
json.published_at post.published_at
Fragment caching for expensive JSON serialization
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
Share this code
Here's the card — post it anywhere.