@tanstack/react-db
Version:
React integration for @tanstack/db
184 lines (183 loc) • 7.58 kB
JavaScript
"use client";
import { useRef, useCallback, useSyncExternalStore } from "react";
import { normalizeLiveQueryWindowPageSize, getLiveQueryWindowInputKind, compareLiveQueryWindowDependencies, resolveLiveQueryWindowInput, createLiveQueryCollection, getLiveQueryWindowCollectionWarning, assertLiveQueryWindowManyResult, shouldPreserveLiveQueryWindowPageCount, createLiveQueryWindowController, fetchNextLiveQueryWindowPage } from "@tanstack/db";
import { useOptionalDbClient } from "./DbProvider.js";
import { warnDeprecatedDepsArray, prepareDerivedQuery, warnUnhashableDerivedIdentity, prepareQueryValue } from "./useLiveQuery.js";
const DEFAULT_GC_TIME_MS = 1;
const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`);
function useLiveInfiniteQuery(queryFnOrCollection, config, deps) {
const pageSize = normalizeLiveQueryWindowPageSize(config.pageSize);
const initialPageParam = config.initialPageParam ?? 0;
const contextDbClient = useOptionalDbClient();
const dbClient = config.client ?? contextDbClient;
const inputIsCollection = getLiveQueryWindowInputKind(queryFnOrCollection) === `collection`;
const committedRef = useRef(null);
const committed = committedRef.current;
const inputKind = inputIsCollection ? `collection` : `query`;
const derivedIdentityProfilerRef = useRef({
renderCount: 0,
totalMs: 0,
maxMs: 0,
warned: false
});
const legacyUnhashableIdentityRef = useRef([
`legacy-unhashable`
]);
const deferredCollections = /* @__PURE__ */ new Set();
let preparedQueryValue = unpreparedQueryValue;
let identityDeps = [];
let identityMode = `collection`;
if (!inputIsCollection) {
if (config.queryKey !== void 0) {
identityMode = `queryKey`;
identityDeps = config.queryKey;
} else if (deps !== void 0) {
identityMode = `legacyDeps`;
identityDeps = deps;
warnDeprecatedDepsArray(`useLiveInfiniteQuery`);
} else if (committed?.identityMode === `derived` && committed.inputQuery === queryFnOrCollection && committed.client === dbClient) {
identityMode = `derived`;
identityDeps = committed.dependencies ?? [];
} else {
identityMode = `derived`;
const preparation = prepareDerivedQuery(
queryFnOrCollection,
dbClient,
derivedIdentityProfilerRef.current,
deferredCollections
);
preparedQueryValue = preparation.value;
if (preparation.status === `hashable`) {
identityDeps = preparation.identityDeps;
} else {
warnUnhashableDerivedIdentity(preparation.error);
identityDeps = legacyUnhashableIdentityRef.current;
}
}
}
const usesLegacyDeps = !inputIsCollection && config.queryKey === void 0 && deps !== void 0;
const dependencyComparison = compareLiveQueryWindowDependencies(
committed?.dependencies,
identityDeps
);
const sameClient = committed?.client === dbClient;
const dependenciesChanged = !inputIsCollection && (!sameClient || (usesLegacyDeps ? dependencyComparison.changed : !dependencyComparison.structurallyEqual));
const dependenciesStructurallyEqual = usesLegacyDeps && sameClient && dependencyComparison.structurallyEqual;
const needsNewCollection = committed === null || committed.inputKind !== inputKind || inputIsCollection && committed.inputCollection !== queryFnOrCollection || dependenciesChanged;
const pageShapeChanged = committed === null || committed.pageSize !== pageSize || committed.initialPageParam !== initialPageParam;
const needsNewController = committed === null || needsNewCollection || pageShapeChanged;
let renderState = committed;
if (needsNewController) {
let collection = committed?.collection;
let warning = null;
if (needsNewCollection) {
let inputValue = queryFnOrCollection;
if (!inputIsCollection) {
if (preparedQueryValue === unpreparedQueryValue) {
preparedQueryValue = prepareQueryValue(
queryFnOrCollection,
dbClient,
deferredCollections
);
}
inputValue = () => preparedQueryValue;
}
const input = resolveLiveQueryWindowInput(inputValue);
if (input.kind === `collection`) {
collection = input.collection;
} else {
collection = createLiveQueryCollection({
query: input.query.limit(pageSize + 1).offset(0),
// Construction happens during render. Synchronization starts only when
// useSyncExternalStore commits the controller subscription.
startSync: false,
gcTime: DEFAULT_GC_TIME_MS
});
}
}
if (!collection) {
throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`);
}
if (inputIsCollection) {
warning = getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null;
} else {
assertLiveQueryWindowManyResult(collection);
}
const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({
hasPreviousController: committed !== null,
previousInputKind: committed?.inputKind,
inputKind,
sameCollection: inputIsCollection && committed?.inputCollection === collection,
dependenciesChanged,
dependenciesStructurallyEqual,
pageShapeChanged
});
const previousPageCount = committed ? Math.max(1, committed.controller.getSnapshot().pages.length) : 1;
const initialPageCount = canPreservePageCount ? previousPageCount : 1;
renderState = {
inputKind,
inputCollection: inputIsCollection ? collection : null,
inputQuery: inputIsCollection ? null : queryFnOrCollection,
client: dbClient,
identityMode,
dependencies: inputIsCollection ? null : [...identityDeps],
pageSize,
initialPageParam,
collection,
controller: createLiveQueryWindowController(collection, {
pageSize,
initialPageParam,
initialPageCount
}),
warning,
warned: false,
deferredCollections
};
}
const currentRenderState = renderState;
const controller = currentRenderState.controller;
const subscribe = useCallback(
(onStoreChange) => {
const unsubscribe = controller.subscribe(onStoreChange);
committedRef.current = currentRenderState;
if (currentRenderState.warning && !currentRenderState.warned) {
currentRenderState.warned = true;
console.warn(currentRenderState.warning);
}
for (const collection of currentRenderState.deferredCollections) {
collection._resumeSyncStart();
}
currentRenderState.deferredCollections.clear();
return unsubscribe;
},
[controller, currentRenderState]
);
const getSnapshot = useCallback(() => controller.getSnapshot(), [controller]);
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const fetchNextPage = useCallback(
() => fetchNextLiveQueryWindowPage(controller),
[controller]
);
return {
data: snapshot.data,
state: snapshot.state,
status: snapshot.status,
isLoading: snapshot.isLoading,
isReady: snapshot.isReady,
isIdle: snapshot.isIdle,
isError: snapshot.isError,
isCleanedUp: snapshot.isCleanedUp,
collection: snapshot.collection,
isEnabled: snapshot.isEnabled,
pages: snapshot.pages,
pageParams: snapshot.pageParams,
fetchNextPage,
hasNextPage: snapshot.hasNextPage,
isFetchingNextPage: snapshot.isFetchingNextPage,
error: snapshot.error
};
}
export {
useLiveInfiniteQuery
};
//# sourceMappingURL=useLiveInfiniteQuery.js.map