@preact-signals/query
Version:
A reactive utility for React/Preact that simplifies the handling of data fetching and state management. Powered by Preact Signals, it provides hooks and functions to create reactive resources and manage their state seamlessly.
125 lines (107 loc) • 3.65 kB
text/typescript
"use client";
import * as React from "react";
import { useSyncExternalStore } from "./useSyncExternalStore.ts";
import type { QueryKey, QueryObserver, QueryObserverResult } from "@tanstack/query-core";
import { notifyManager } from "@tanstack/query-core";
import { useQueryClient } from "./QueryClientProvider.tsx";
import { useQueryErrorResetBoundary } from "./QueryErrorResetBoundary.tsx";
import {
ensurePreventErrorBoundaryRetry,
getHasError,
useClearResetErrorBoundary,
} from "./errorBoundaryUtils.ts";
import { useIsRestoring } from "./isRestoring.tsx";
import { ensureStaleTime, fetchOptimistic, shouldSuspend } from "./suspense.ts";
import type { UseBaseQueryOptions } from "./types.ts";
export function useBaseQuery<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey extends QueryKey
>(
options: UseBaseQueryOptions<
TQueryFnData,
TError,
TData,
TQueryData,
TQueryKey
>,
Observer: typeof QueryObserver
): QueryObserverResult<TData, TError> {
const queryClient = useQueryClient({ context: options.context });
const isRestoring = useIsRestoring();
const errorResetBoundary = useQueryErrorResetBoundary();
const defaultedOptions = queryClient.defaultQueryOptions(options);
// Make sure results are optimistically set in fetching state before subscribing or updating options
defaultedOptions._optimisticResults = isRestoring
? "isRestoring"
: "optimistic";
// Include callbacks in batch renders
if (defaultedOptions.onError) {
defaultedOptions.onError = notifyManager.batchCalls(
defaultedOptions.onError
);
}
if (defaultedOptions.onSuccess) {
defaultedOptions.onSuccess = notifyManager.batchCalls(
defaultedOptions.onSuccess
);
}
if (defaultedOptions.onSettled) {
defaultedOptions.onSettled = notifyManager.batchCalls(
defaultedOptions.onSettled
);
}
ensureStaleTime(defaultedOptions);
ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary);
useClearResetErrorBoundary(errorResetBoundary);
const [observer] = React.useState(
() =>
new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(
queryClient,
defaultedOptions
)
);
const result = observer.getOptimisticResult(defaultedOptions);
useSyncExternalStore(
React.useCallback(
(onStoreChange) => {
const unsubscribe = isRestoring
? () => undefined
: observer.subscribe(notifyManager.batchCalls(onStoreChange));
// Update result to make sure we did not miss any query updates
// between creating the observer and subscribing to it.
observer.updateResult();
return unsubscribe;
},
[observer, isRestoring]
),
() => observer.getCurrentResult(),
() => observer.getCurrentResult()
);
React.useEffect(() => {
// Do not notify on updates because of changes in the options because
// these changes should already be reflected in the optimistic result.
observer.setOptions(defaultedOptions, { listeners: false });
}, [defaultedOptions, observer]);
// Handle suspense
if (shouldSuspend(defaultedOptions, result, isRestoring)) {
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary);
}
// Handle error boundary
if (
getHasError({
result,
errorResetBoundary,
useErrorBoundary: defaultedOptions.useErrorBoundary,
query: observer.getCurrentQuery(),
})
) {
throw result.error;
}
// Handle result property usage tracking
return !defaultedOptions.notifyOnChangeProps
? observer.trackResult(result)
: result;
}