Skip to main content

declarative-queries

SuspenseQuery, SuspenseQueries, SuspenseInfiniteQuery render-prop components for fetching in JSX without hook wrapper components. Load when placing queries at the same depth as Suspense/ErrorBoundary, running parallel queries, using select, integrating queryOptions/infiniteQueryOptions, or replacing useSuspenseQuery wrapper components.

Quellinformationen

Repository
toss/suspensive
Letzte Quellaktivität
4. Juli 2026 um 07:04
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.059
Forks
99

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
declarative-queries
description
SuspenseQuery, SuspenseQueries, SuspenseInfiniteQuery render-prop components for fetching in JSX without hook wrapper components. Load when placing queries at the same depth as Suspense/ErrorBoundary, running parallel queries, using select, integrating queryOptions/infiniteQueryOptions, or replacing useSuspenseQuery wrapper components.
metadata
{"type":"sub-skill","library":"@suspensive/react-query-4","library_version":"3.21.2","framework":"react"}
requires
["react-query"]
sources
["toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/SuspenseQuery.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/SuspenseQueries.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/SuspenseInfiniteQuery.mdx","toss/suspensive:docs/suspensive.org/src/content/en/docs/react-query/motivation.mdx"]
This skill builds on react-query. Read ../SKILL.md first. SuspenseQuery/SuspenseQueries/SuspenseInfiniteQuery move data fetching into JSX so what suspends is visible at the same depth as the Suspense and ErrorBoundary handling it, and child components stay presentational. ## Setup ```bash npm install @suspensive/react-query-4 @suspensive/react @tanstack/react-query@4 ``` Components come from '@suspensive/react-query-4'; queryOptions/infiniteQueryOptions come from '@tanstack/react-query' (official in TSQ 4.40+/4.41+; the Suspensive re-exports are deprecated). ## Core Patterns ### Parallel queries at one depth with SuspenseQuery ```tsx 'use client' import { ErrorBoundary, Suspense } from '@suspensive/react' import { SuspenseQuery } from '@suspensive/react-query-4' import { PostListItem, UserProfile } from '~/components' import { postsQueryOptions, userQueryOptions } from '~/queries' export const PostsPage = ({ userId }: { userId: number }) => ( <ErrorBoundary fallback={({ error }) => <>{error.message}</>}> <Suspense fallback={'loading...'}> <SuspenseQuery {...userQueryOptions(userId)}> {({ data: user }) => <UserProfile key={user.id} {...user} />} </SuspenseQuery> <SuspenseQuery {...postsQueryOptions(userId)} select={(posts) => posts.filter(({ isPublic }) => isPublic)}> {({ data: posts }) => posts.map((post) => <PostListItem key={post.id} {...post} />)} </SuspenseQuery> </Suspense> </ErrorBoundary> ) ``` Both queries fetch in parallel; the boundary scope is obvious because both sources of suspension sit directly under `Suspense`. ### Combined results with SuspenseQueries ```tsx 'use client' import { ErrorBoundary, Suspense } from '@suspensive/react' import { SuspenseQueries } from '@suspensive/react-query-4' import { PostListItem, UserProfile } from '~/components' import { postsQueryOptions, userQueryOptions } from '~/queries' export const PostsPage = ({ userId }: { userId: number }) => ( <ErrorBoundary fallback={({ error }) => <>{error.message}</>}> <Suspense fallback={'loading...'}> <SuspenseQueries queries={[userQueryOptions(userId), postsQueryOptions(userId)]}> {([{ data: user }, { data: posts }]) => ( <> <UserProfile {...user} /> {posts.map((post) => ( <PostListItem key={post.id} {...post} /> ))} </> )} </SuspenseQueries> </Suspense> </ErrorBoundary> ) ``` Use SuspenseQueries when one render needs both results together (useSuspenseQueries in JSX). ### Infinite scrolling with SuspenseInfiniteQuery TSQ v4 infinite queries take no `initialPageParam` — default the cursor in the queryFn signature instead. ```tsx 'use client' import { ErrorBoundary, Suspense } from '@suspensive/react' import { SuspenseInfiniteQuery } from '@suspensive/react-query-4' import { infiniteQueryOptions } from '@tanstack/react-query' import { PostListItem } from '~/components' import { getPosts } from '~/api' const postsInfiniteQueryOptions = (userId: number) => infiniteQueryOptions({ queryKey: ['users', userId, 'posts'], queryFn: ({ pageParam = 0 }) => getPosts(userId, pageParam), getNextPageParam: (lastPage) => lastPage.nextCursor, }) export const InfinitePostsPage = ({ userId }: { userId: number }) => ( <ErrorBoundary fallback={({ error }) => <>{error.message}</>}> <Suspense fallback={'loading...'}> <SuspenseInfiniteQuery {...postsInfiniteQueryOptions(userId)}> {({ data, fetchNextPage, hasNextPage }) => ( <> {data.pages.flatMap((page) => page.posts.map((post) => <PostListItem key={post.id} {...post} />))} <button type="button" disabled={!hasNextPage} onClick={() => fetchNextPage()}> Load More </button> </> )} </SuspenseInfiniteQuery> </Suspense> </ErrorBoundary> ) ``` ## Common Mistakes ### [MEDIUM] Wrapper components created only to call useSuspenseQuery Wrong: ```tsx const UserInfo = ({ userId }: { userId: number }) => { const { data } = useSuspenseQuery(userQueryOptions(userId)) return <UserProfile {...data} /> } ``` Correct: ```tsx <SuspenseQuery {...userQueryOptions(userId)}>{({ data }) => <UserProfile {...data} />}</SuspenseQuery> ``` Hooks force an extra child component under Suspense whose name hides that it suspends; SuspenseQuery keeps fetching visible in JSX so only presentational components remain. Source: docs/suspensive.org/src/content/en/docs/react-query/motivation.mdx ### [HIGH] SuspenseQuery without an ancestor Suspense Wrong: ```tsx <SuspenseQuery {...postQueryOptions(id)}>{({ data }) => <Post data={data} />}</SuspenseQuery> ``` Correct: ```tsx <Suspense fallback={<PostSkeleton />}> <SuspenseQuery {...postQueryOptions(id)}>{({ data }) => <Post data={data} />}</SuspenseQuery> </Suspense> ``` The component suspends; with no boundary above it, suspension bubbles to the nearest ancestor Suspense or the app root, blanking unrelated UI. Source: https://github.com/toss/suspensive/issues/1654 ### [HIGH] Rendering SuspenseQuery in a React Server Component Wrong: ```tsx // app/page.tsx — Server Component import { SuspenseQuery } from '@suspensive/react-query-4' export default function Page() { return ( <SuspenseQuery queryKey={['posts']} queryFn={getPosts}> {({ data }) => <PostList posts={data} />} </SuspenseQuery> ) } ``` Correct: ```tsx 'use client' import { SuspenseQuery } from '@suspensive/react-query-4' export const Posts = () => ( <SuspenseQuery queryKey={['posts']} queryFn={getPosts}> {({ data }) => <PostList posts={data} />} </SuspenseQuery> ) ``` Render-prop children are functions and cannot cross the RSC serialization boundary; keep these components inside 'use client' modules. Source: https://github.com/toss/suspensive/issues/1563 ### [HIGH] Passing enabled or placeholderData to suspense queries Wrong: ```tsx <SuspenseQuery queryKey={['user', userId]} queryFn={getUser} enabled={!!userId}> {({ data }) => <UserProfile {...data} />} </SuspenseQuery> ``` Correct: ```tsx { userId ? ( <SuspenseQuery queryKey={['user', userId]} queryFn={() => getUser(userId)}> {({ data }) => <UserProfile {...data} />} </SuspenseQuery> ) : null } ``` Suspense queries must guarantee data, so enabled/placeholderData are excluded from their option types — express conditionality with JSX instead. Source: docs/suspensive.org/src/content/en/docs/react-query/migration (v2 breaking changes) ### [MEDIUM] Checking isLoading/isError on suspense query results Wrong: ```tsx <SuspenseQuery {...postsQueryOptions()}> {({ data, isLoading }) => (isLoading ? <Spinner /> : <List data={data} />)} </SuspenseQuery> ``` Correct: ```tsx <SuspenseQuery {...postsQueryOptions()}>{({ data }) => <List data={data} />}</SuspenseQuery> ``` Suspense query result types have no loading/error branches — `data` is always the success type because Suspense and ErrorBoundary guarantee it, so guards are dead code. Source: docs/suspensive.org/src/content/en/docs/react-query/motivation.mdx See also: ../mutations/SKILL.md for Mutation in list rows, ../prefetching/SKILL.md to warm the cache before these components suspend, ../../compositions/suspensive-react/SKILL.md for ErrorBoundary reset wiring around SuspenseQuery.
Auf GitHub ansehen