diff --git a/.changeset/suspense-live-query-undefined-support.md b/.changeset/suspense-live-query-undefined-support.md new file mode 100644 index 000000000..a6a97f814 --- /dev/null +++ b/.changeset/suspense-live-query-undefined-support.md @@ -0,0 +1,57 @@ +--- +"@tanstack/react-db": patch +--- + +Improve runtime error message and documentation when `useLiveSuspenseQuery` receives `undefined` from query callback. + +Following TanStack Query's `useSuspenseQuery` design, `useLiveSuspenseQuery` intentionally does not support disabled queries (when callback returns `undefined` or `null`). This maintains the type guarantee that `data` is always `T` (not `T | undefined`), which is a core benefit of using Suspense. + +**What changed:** + +1. **Improved runtime error message** with clear guidance: + +``` +useLiveSuspenseQuery does not support disabled queries (callback returned undefined/null). +The Suspense pattern requires data to always be defined (T, not T | undefined). +Solutions: +1) Use conditional rendering - don't render the component until the condition is met. +2) Use useLiveQuery instead, which supports disabled queries with the 'isEnabled' flag. +``` + +2. **Enhanced JSDoc documentation** with detailed `@remarks` section explaining the design decision, showing both incorrect (❌) and correct (✅) patterns + +**Why this matters:** + +```typescript +// ❌ This pattern doesn't work with Suspense queries: +const { data } = useLiveSuspenseQuery( + (q) => userId + ? q.from({ users }).where(({ users }) => eq(users.id, userId)).findOne() + : undefined, + [userId] +) + +// ✅ Instead, use conditional rendering: +function UserProfile({ userId }: { userId: string }) { + const { data } = useLiveSuspenseQuery( + (q) => q.from({ users }).where(({ users }) => eq(users.id, userId)).findOne(), + [userId] + ) + return
{data.name}
// data is guaranteed non-undefined +} + +function App({ userId }: { userId?: string }) { + if (!userId) return
No user selected
+ return +} + +// ✅ Or use useLiveQuery for conditional queries: +const { data, isEnabled } = useLiveQuery( + (q) => userId + ? q.from({ users }).where(({ users }) => eq(users.id, userId)).findOne() + : undefined, + [userId] +) +``` + +This aligns with TanStack Query's philosophy where Suspense queries prioritize type safety and proper component composition over flexibility. diff --git a/packages/react-db/src/useLiveSuspenseQuery.ts b/packages/react-db/src/useLiveSuspenseQuery.ts index 67163bdc7..f96fe03bd 100644 --- a/packages/react-db/src/useLiveSuspenseQuery.ts +++ b/packages/react-db/src/useLiveSuspenseQuery.ts @@ -71,6 +71,39 @@ import type { * * ) * } + * + * @remarks + * **Important:** This hook does NOT support disabled queries (returning undefined/null). + * Following TanStack Query's useSuspenseQuery design, the query callback must always + * return a valid query, collection, or config object. + * + * ❌ **This will cause a type error:** + * ```ts + * useLiveSuspenseQuery( + * (q) => userId ? q.from({ users }) : undefined // ❌ Error! + * ) + * ``` + * + * ✅ **Use conditional rendering instead:** + * ```ts + * function Profile({ userId }: { userId: string }) { + * const { data } = useLiveSuspenseQuery( + * (q) => q.from({ users }).where(({ users }) => eq(users.id, userId)) + * ) + * return
{data.name}
+ * } + * + * // In parent component: + * {userId ? :
No user
} + * ``` + * + * ✅ **Or use useLiveQuery for conditional queries:** + * ```ts + * const { data, isEnabled } = useLiveQuery( + * (q) => userId ? q.from({ users }) : undefined, // ✅ Supported! + * [userId] + * ) + * ``` */ // Overload 1: Accept query function that always returns QueryBuilder export function useLiveSuspenseQuery( @@ -146,9 +179,13 @@ export function useLiveSuspenseQuery( // SUSPENSE LOGIC: Throw promise or error based on collection status // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!result.isEnabled) { - // Suspense queries cannot be disabled - throw error + // Suspense queries cannot be disabled - this matches TanStack Query's useSuspenseQuery behavior throw new Error( - `useLiveSuspenseQuery does not support disabled queries. Use useLiveQuery instead for conditional queries.` + `useLiveSuspenseQuery does not support disabled queries (callback returned undefined/null). ` + + `The Suspense pattern requires data to always be defined (T, not T | undefined). ` + + `Solutions: ` + + `1) Use conditional rendering - don't render the component until the condition is met. ` + + `2) Use useLiveQuery instead, which supports disabled queries with the 'isEnabled' flag.` ) }