UNPKG

@tanstack/react-db

Version:

React integration for @tanstack/db

236 lines (235 loc) • 9.6 kB
"use client"; import { useRef, useCallback, useSyncExternalStore } from "react"; import { normalizeLiveQueryWindowPageSize, getLiveQueryWindowInputKind, compareLiveQueryWindowDependencies, shouldPreserveLiveQueryWindowPageCount, liveQueryWindowMatches, resolveLiveQueryWindowInput, createLiveQueryCollection, getLiveQueryWindowCollectionWarning, assertLiveQueryWindowManyResult, createLiveQueryWindowController, fetchNextLiveQueryWindowPage } from "@tanstack/db"; import { useOptionalDbClient } from "./DbProvider.js"; import { claimSourceIds, createSourceIdBindings, resumeSyncStarts } from "./source-id-bindings.js"; import { warnDeprecatedDepsArray, prepareDerivedQuery, warnUnhashableDerivedIdentity, prepareQueryValue } from "./useLiveQuery.js"; const DEFAULT_GC_TIME_MS = 1; const unpreparedQueryValue = /* @__PURE__ */ Symbol(`unpreparedQueryValue`); function queryIdentityMatches(comparison, sameClient, usesLegacyDeps) { return sameClient && (usesLegacyDeps ? !comparison.changed : comparison.structurallyEqual); } function useLiveInfiniteQuery(queryFnOrCollection, config, deps) { if (`getNextPageParam` in config) { throw new Error( `getNextPageParam is not supported by useLiveInfiniteQuery. Use an on-demand collection and fulfill meta.loadSubsetOptions in queryFn for server pagination.` ); } 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 renderedCollectionRef = useRef(null); const sourceIdsRef = useRef(null); 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; claimSourceIds( sourceIdsRef.current ??= createSourceIdBindings(), preparation.value, dbClient, `useLiveInfiniteQuery`, () => resumeSyncStarts(deferredCollections) ); 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 && !queryIdentityMatches(dependencyComparison, sameClient, usesLegacyDeps); 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; let startInRender = false; let suppliedCollection = false; let createdCollection = false; const canPreservePageCount = shouldPreserveLiveQueryWindowPageCount({ hasPreviousController: committed !== null, previousInputKind: committed?.inputKind, inputKind, sameCollection: inputIsCollection && committed?.inputCollection === queryFnOrCollection, dependenciesChanged, dependenciesStructurallyEqual, pageShapeChanged }); const previousPageCount = committed ? Math.max(1, committed.controller.getSnapshot().pages.length) : 1; const initialPageCount = canPreservePageCount ? previousPageCount : 1; const requiredLimit = initialPageCount * pageSize + 1; if (needsNewCollection) { let inputValue = queryFnOrCollection; if (!inputIsCollection) { if (preparedQueryValue === unpreparedQueryValue) { preparedQueryValue = prepareQueryValue( queryFnOrCollection, dbClient, deferredCollections ); } inputValue = () => preparedQueryValue; } const rendered = renderedCollectionRef.current; if (!inputIsCollection && rendered !== null && queryIdentityMatches( compareLiveQueryWindowDependencies( rendered.dependencies, identityDeps ), rendered.client === dbClient, usesLegacyDeps ) && rendered.collection.status !== `cleaned-up` && rendered.collection.status !== `error` && liveQueryWindowMatches(rendered.collection, requiredLimit)) { collection = rendered.collection; for (const deferred of rendered.deferredCollections) { deferredCollections.add(deferred); } } else { const input = resolveLiveQueryWindowInput(inputValue); if (input.kind === `collection`) { collection = input.collection; suppliedCollection = true; renderedCollectionRef.current = null; } else { collection = createLiveQueryCollection({ query: input.query.limit(requiredLimit).offset(0), gcTime: DEFAULT_GC_TIME_MS }); startInRender = true; createdCollection = true; } } } if (!collection) { throw new Error(`useLiveInfiniteQuery: Failed to create a collection.`); } if (inputIsCollection) { warning = getLiveQueryWindowCollectionWarning(collection, pageSize + 1) ?? null; } else { assertLiveQueryWindowManyResult(collection); } if (suppliedCollection) { startInRender = liveQueryWindowMatches(collection, requiredLimit); } if (startInRender) collection.startSyncImmediate(); if (createdCollection) { renderedCollectionRef.current = { client: dbClient, dependencies: [...identityDeps], collection, deferredCollections }; } 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); } resumeSyncStarts(currentRenderState.deferredCollections); 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, get state() { return snapshot.state; }, status: snapshot.status, isLoading: snapshot.isLoading, isReady: snapshot.isReady, persistedStatus: snapshot.persistedStatus, isPersistedReady: snapshot.isPersistedReady, persistedError: snapshot.persistedError, 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