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.