Prompt
When handling query/mutation results, treat undefined (and null) as semantically distinct and enforce that distinction in both runtime checks and TypeScript types.
Coding standards
- Never use truthy checks for cached/data presence.
- Use explicit comparisons so valid falsy values (
null,false,"") are not misclassified. - Prefer:
value !== undefined/value === undefined
- Use explicit comparisons so valid falsy values (
- Do not resolve/return
undefinedwhere the API contract says it can’t.- Avoid non-null assertions (
!) that hide cancelled/empty states. - Add explicit branches for “cancelled/skip” paths.
- Avoid non-null assertions (
- For “skip”/sentinel control tokens, don’t treat them as normal data that returns
undefined.- Prefer rejecting/throwing (or preventing execution) so that the invariant is preserved.
- Align types with runtime: encode “undefined not allowed” in generics, and avoid type-widening fallbacks.
- Prefer precise
undefined/optional handling over{}-style fallbacks that widen unions.
- Prefer precise
Example
// 1) Presence checks: don’t use truthy
const data = query.state.data
const hasData = data !== undefined // null/false/"" are valid
// 2) Imperative fetch contract: never resolve undefined
async function fetchQueryImperative() {
if (initialFetchCancelled) {
throw new Error('Cancelled')
}
// safe: data is guaranteed by control flow
return data
}
// 3) Skip token: don’t return undefined as “success data”
if (queryFn === skipToken) {
return Promise.reject(
new Error('Attempted to invoke queryFn when set to skipToken')
)
}
Applying these rules prevents subtle SSR/hydration mismatches, avoids incorrect cache restore decisions, and keeps null/undefined behavior predictable across the codebase.