@tanstack/react-db 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/useLiveInfiniteQuery.cjs +77 -0
- package/dist/cjs/useLiveInfiniteQuery.cjs.map +1 -0
- package/dist/cjs/useLiveInfiniteQuery.d.cts +59 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/useLiveInfiniteQuery.d.ts +59 -0
- package/dist/esm/useLiveInfiniteQuery.js +77 -0
- package/dist/esm/useLiveInfiniteQuery.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +1 -0
- package/src/useLiveInfiniteQuery.ts +185 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const useLiveQuery = require("./useLiveQuery.cjs");
|
|
4
|
+
const useLiveInfiniteQuery = require("./useLiveInfiniteQuery.cjs");
|
|
4
5
|
const db = require("@tanstack/db");
|
|
5
6
|
exports.useLiveQuery = useLiveQuery.useLiveQuery;
|
|
7
|
+
exports.useLiveInfiniteQuery = useLiveInfiniteQuery.useLiveInfiniteQuery;
|
|
6
8
|
Object.defineProperty(exports, "createTransaction", {
|
|
7
9
|
enumerable: true,
|
|
8
10
|
get: () => db.createTransaction
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const useLiveQuery = require("./useLiveQuery.cjs");
|
|
5
|
+
function isLiveQueryCollectionUtils(utils) {
|
|
6
|
+
return typeof utils.setWindow === `function`;
|
|
7
|
+
}
|
|
8
|
+
function useLiveInfiniteQuery(queryFn, config, deps = []) {
|
|
9
|
+
const pageSize = config.pageSize || 20;
|
|
10
|
+
const initialPageParam = config.initialPageParam ?? 0;
|
|
11
|
+
const [loadedPageCount, setLoadedPageCount] = react.useState(1);
|
|
12
|
+
const [isFetchingNextPage, setIsFetchingNextPage] = react.useState(false);
|
|
13
|
+
const depsKey = JSON.stringify(deps);
|
|
14
|
+
const prevDepsKeyRef = react.useRef(depsKey);
|
|
15
|
+
react.useEffect(() => {
|
|
16
|
+
if (prevDepsKeyRef.current !== depsKey) {
|
|
17
|
+
setLoadedPageCount(1);
|
|
18
|
+
prevDepsKeyRef.current = depsKey;
|
|
19
|
+
}
|
|
20
|
+
}, [depsKey]);
|
|
21
|
+
const queryResult = useLiveQuery.useLiveQuery(
|
|
22
|
+
(q) => queryFn(q).limit(pageSize).offset(0),
|
|
23
|
+
deps
|
|
24
|
+
);
|
|
25
|
+
react.useEffect(() => {
|
|
26
|
+
const newLimit = loadedPageCount * pageSize + 1;
|
|
27
|
+
const utils = queryResult.collection.utils;
|
|
28
|
+
if (isLiveQueryCollectionUtils(utils)) {
|
|
29
|
+
const result = utils.setWindow({ offset: 0, limit: newLimit });
|
|
30
|
+
if (result !== true) {
|
|
31
|
+
setIsFetchingNextPage(true);
|
|
32
|
+
result.then(() => {
|
|
33
|
+
setIsFetchingNextPage(false);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
setIsFetchingNextPage(false);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [loadedPageCount, pageSize, queryResult.collection]);
|
|
40
|
+
const { pages, pageParams, hasNextPage, flatData } = react.useMemo(() => {
|
|
41
|
+
const dataArray = queryResult.data;
|
|
42
|
+
const totalItemsRequested = loadedPageCount * pageSize;
|
|
43
|
+
const hasMore = dataArray.length > totalItemsRequested;
|
|
44
|
+
const pagesResult = [];
|
|
45
|
+
const pageParamsResult = [];
|
|
46
|
+
for (let i = 0; i < loadedPageCount; i++) {
|
|
47
|
+
const pageData = dataArray.slice(i * pageSize, (i + 1) * pageSize);
|
|
48
|
+
pagesResult.push(pageData);
|
|
49
|
+
pageParamsResult.push(initialPageParam + i);
|
|
50
|
+
}
|
|
51
|
+
const flatDataResult = dataArray.slice(
|
|
52
|
+
0,
|
|
53
|
+
totalItemsRequested
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
pages: pagesResult,
|
|
57
|
+
pageParams: pageParamsResult,
|
|
58
|
+
hasNextPage: hasMore,
|
|
59
|
+
flatData: flatDataResult
|
|
60
|
+
};
|
|
61
|
+
}, [queryResult.data, loadedPageCount, pageSize, initialPageParam]);
|
|
62
|
+
const fetchNextPage = react.useCallback(() => {
|
|
63
|
+
if (!hasNextPage || isFetchingNextPage) return;
|
|
64
|
+
setLoadedPageCount((prev) => prev + 1);
|
|
65
|
+
}, [hasNextPage, isFetchingNextPage]);
|
|
66
|
+
return {
|
|
67
|
+
...queryResult,
|
|
68
|
+
data: flatData,
|
|
69
|
+
pages,
|
|
70
|
+
pageParams,
|
|
71
|
+
fetchNextPage,
|
|
72
|
+
hasNextPage,
|
|
73
|
+
isFetchingNextPage
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
exports.useLiveInfiniteQuery = useLiveInfiniteQuery;
|
|
77
|
+
//# sourceMappingURL=useLiveInfiniteQuery.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLiveInfiniteQuery.cjs","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { useLiveQuery } from \"./useLiveQuery\"\nimport type {\n Context,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionUtils,\n QueryBuilder,\n} from \"@tanstack/db\"\n\n/**\n * Type guard to check if utils object has setWindow method (LiveQueryCollectionUtils)\n */\nfunction isLiveQueryCollectionUtils(\n utils: unknown\n): utils is LiveQueryCollectionUtils {\n return typeof (utils as any).setWindow === `function`\n}\n\nexport type UseLiveInfiniteQueryConfig<TContext extends Context> = {\n pageSize?: number\n initialPageParam?: number\n getNextPageParam: (\n lastPage: Array<InferResultType<TContext>[number]>,\n allPages: Array<Array<InferResultType<TContext>[number]>>,\n lastPageParam: number,\n allPageParams: Array<number>\n ) => number | undefined\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => void\n hasNextPage: boolean\n isFetchingNextPage: boolean\n}\n\n/**\n * Create an infinite query using a query function with live updates\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and getNextPageParam\n * @param deps - Array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n *\n * @example\n * // Basic infinite query\n * const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .orderBy(({ posts }) => posts.createdAt, 'desc')\n * .select(({ posts }) => ({\n * id: posts.id,\n * title: posts.title\n * })),\n * {\n * pageSize: 20,\n * getNextPageParam: (lastPage, allPages) =>\n * lastPage.length === 20 ? allPages.length : undefined\n * }\n * )\n *\n * @example\n * // With dependencies\n * const { pages, fetchNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .where(({ posts }) => eq(posts.category, category))\n * .orderBy(({ posts }) => posts.createdAt, 'desc'),\n * {\n * pageSize: 10,\n * getNextPageParam: (lastPage) =>\n * lastPage.length === 10 ? lastPage.length : undefined\n * },\n * [category]\n * )\n */\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps: Array<unknown> = []\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageSize = config.pageSize || 20\n const initialPageParam = config.initialPageParam ?? 0\n\n // Track how many pages have been loaded\n const [loadedPageCount, setLoadedPageCount] = useState(1)\n const [isFetchingNextPage, setIsFetchingNextPage] = useState(false)\n\n // Stringify deps for comparison\n const depsKey = JSON.stringify(deps)\n const prevDepsKeyRef = useRef(depsKey)\n\n // Reset page count when dependencies change\n useEffect(() => {\n if (prevDepsKeyRef.current !== depsKey) {\n setLoadedPageCount(1)\n prevDepsKeyRef.current = depsKey\n }\n }, [depsKey])\n\n // Create a live query with initial limit and offset\n // The query function is wrapped to add limit/offset to the query\n const queryResult = useLiveQuery(\n (q) => queryFn(q).limit(pageSize).offset(0),\n deps\n )\n\n // Update the window when loadedPageCount changes\n // We fetch one extra item to peek if there's a next page\n useEffect(() => {\n const newLimit = loadedPageCount * pageSize + 1 // +1 to peek ahead\n const utils = queryResult.collection.utils\n // setWindow is available on live query collections with orderBy\n if (isLiveQueryCollectionUtils(utils)) {\n const result = utils.setWindow({ offset: 0, limit: newLimit })\n // setWindow returns true if data is immediately available, or Promise<void> if loading\n if (result !== true) {\n setIsFetchingNextPage(true)\n result.then(() => {\n setIsFetchingNextPage(false)\n })\n } else {\n setIsFetchingNextPage(false)\n }\n }\n }, [loadedPageCount, pageSize, queryResult.collection])\n\n // Split the data array into pages and determine if there's a next page\n const { pages, pageParams, hasNextPage, flatData } = useMemo(() => {\n const dataArray = queryResult.data as InferResultType<TContext>\n const totalItemsRequested = loadedPageCount * pageSize\n\n // Check if we have more data than requested (the peek ahead item)\n const hasMore = dataArray.length > totalItemsRequested\n\n // Build pages array (without the peek ahead item)\n const pagesResult: Array<Array<InferResultType<TContext>[number]>> = []\n const pageParamsResult: Array<number> = []\n\n for (let i = 0; i < loadedPageCount; i++) {\n const pageData = dataArray.slice(i * pageSize, (i + 1) * pageSize)\n pagesResult.push(pageData)\n pageParamsResult.push(initialPageParam + i)\n }\n\n // Flatten the pages for the data return (without peek ahead item)\n const flatDataResult = dataArray.slice(\n 0,\n totalItemsRequested\n ) as InferResultType<TContext>\n\n return {\n pages: pagesResult,\n pageParams: pageParamsResult,\n hasNextPage: hasMore,\n flatData: flatDataResult,\n }\n }, [queryResult.data, loadedPageCount, pageSize, initialPageParam])\n\n // Fetch next page\n const fetchNextPage = useCallback(() => {\n if (!hasNextPage || isFetchingNextPage) return\n\n setLoadedPageCount((prev) => prev + 1)\n }, [hasNextPage, isFetchingNextPage])\n\n return {\n ...queryResult,\n data: flatData,\n pages,\n pageParams,\n fetchNextPage,\n hasNextPage,\n isFetchingNextPage,\n }\n}\n"],"names":["useState","useRef","useEffect","useLiveQuery","useMemo","useCallback"],"mappings":";;;;AAaA,SAAS,2BACP,OACmC;AACnC,SAAO,OAAQ,MAAc,cAAc;AAC7C;AAoEO,SAAS,qBACd,SACA,QACA,OAAuB,CAAA,GACe;AACtC,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,mBAAmB,OAAO,oBAAoB;AAGpD,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,MAAAA,SAAS,CAAC;AACxD,QAAM,CAAC,oBAAoB,qBAAqB,IAAIA,MAAAA,SAAS,KAAK;AAGlE,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAM,iBAAiBC,MAAAA,OAAO,OAAO;AAGrCC,QAAAA,UAAU,MAAM;AACd,QAAI,eAAe,YAAY,SAAS;AACtC,yBAAmB,CAAC;AACpB,qBAAe,UAAU;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAIZ,QAAM,cAAcC,aAAAA;AAAAA,IAClB,CAAC,MAAM,QAAQ,CAAC,EAAE,MAAM,QAAQ,EAAE,OAAO,CAAC;AAAA,IAC1C;AAAA,EAAA;AAKFD,QAAAA,UAAU,MAAM;AACd,UAAM,WAAW,kBAAkB,WAAW;AAC9C,UAAM,QAAQ,YAAY,WAAW;AAErC,QAAI,2BAA2B,KAAK,GAAG;AACrC,YAAM,SAAS,MAAM,UAAU,EAAE,QAAQ,GAAG,OAAO,UAAU;AAE7D,UAAI,WAAW,MAAM;AACnB,8BAAsB,IAAI;AAC1B,eAAO,KAAK,MAAM;AAChB,gCAAsB,KAAK;AAAA,QAC7B,CAAC;AAAA,MACH,OAAO;AACL,8BAAsB,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,UAAU,YAAY,UAAU,CAAC;AAGtD,QAAM,EAAE,OAAO,YAAY,aAAa,SAAA,IAAaE,MAAAA,QAAQ,MAAM;AACjE,UAAM,YAAY,YAAY;AAC9B,UAAM,sBAAsB,kBAAkB;AAG9C,UAAM,UAAU,UAAU,SAAS;AAGnC,UAAM,cAA+D,CAAA;AACrE,UAAM,mBAAkC,CAAA;AAExC,aAAS,IAAI,GAAG,IAAI,iBAAiB,KAAK;AACxC,YAAM,WAAW,UAAU,MAAM,IAAI,WAAW,IAAI,KAAK,QAAQ;AACjE,kBAAY,KAAK,QAAQ;AACzB,uBAAiB,KAAK,mBAAmB,CAAC;AAAA,IAC5C;AAGA,UAAM,iBAAiB,UAAU;AAAA,MAC/B;AAAA,MACA;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,EAEd,GAAG,CAAC,YAAY,MAAM,iBAAiB,UAAU,gBAAgB,CAAC;AAGlE,QAAM,gBAAgBC,MAAAA,YAAY,MAAM;AACtC,QAAI,CAAC,eAAe,mBAAoB;AAExC,uBAAmB,CAAC,SAAS,OAAO,CAAC;AAAA,EACvC,GAAG,CAAC,aAAa,kBAAkB,CAAC;AAEpC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useLiveQuery } from './useLiveQuery.cjs';
|
|
2
|
+
import { Context, InferResultType, InitialQueryBuilder, QueryBuilder } from '@tanstack/db';
|
|
3
|
+
export type UseLiveInfiniteQueryConfig<TContext extends Context> = {
|
|
4
|
+
pageSize?: number;
|
|
5
|
+
initialPageParam?: number;
|
|
6
|
+
getNextPageParam: (lastPage: Array<InferResultType<TContext>[number]>, allPages: Array<Array<InferResultType<TContext>[number]>>, lastPageParam: number, allPageParams: Array<number>) => number | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnType<typeof useLiveQuery<TContext>>, `data`> & {
|
|
9
|
+
data: InferResultType<TContext>;
|
|
10
|
+
pages: Array<Array<InferResultType<TContext>[number]>>;
|
|
11
|
+
pageParams: Array<number>;
|
|
12
|
+
fetchNextPage: () => void;
|
|
13
|
+
hasNextPage: boolean;
|
|
14
|
+
isFetchingNextPage: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Create an infinite query using a query function with live updates
|
|
18
|
+
*
|
|
19
|
+
* Uses `utils.setWindow()` to dynamically adjust the limit/offset window
|
|
20
|
+
* without recreating the live query collection on each page change.
|
|
21
|
+
*
|
|
22
|
+
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
23
|
+
* @param config - Configuration including pageSize and getNextPageParam
|
|
24
|
+
* @param deps - Array of dependencies that trigger query re-execution when changed
|
|
25
|
+
* @returns Object with pages, data, and pagination controls
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Basic infinite query
|
|
29
|
+
* const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
|
|
30
|
+
* (q) => q
|
|
31
|
+
* .from({ posts: postsCollection })
|
|
32
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc')
|
|
33
|
+
* .select(({ posts }) => ({
|
|
34
|
+
* id: posts.id,
|
|
35
|
+
* title: posts.title
|
|
36
|
+
* })),
|
|
37
|
+
* {
|
|
38
|
+
* pageSize: 20,
|
|
39
|
+
* getNextPageParam: (lastPage, allPages) =>
|
|
40
|
+
* lastPage.length === 20 ? allPages.length : undefined
|
|
41
|
+
* }
|
|
42
|
+
* )
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // With dependencies
|
|
46
|
+
* const { pages, fetchNextPage } = useLiveInfiniteQuery(
|
|
47
|
+
* (q) => q
|
|
48
|
+
* .from({ posts: postsCollection })
|
|
49
|
+
* .where(({ posts }) => eq(posts.category, category))
|
|
50
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc'),
|
|
51
|
+
* {
|
|
52
|
+
* pageSize: 10,
|
|
53
|
+
* getNextPageParam: (lastPage) =>
|
|
54
|
+
* lastPage.length === 10 ? lastPage.length : undefined
|
|
55
|
+
* },
|
|
56
|
+
* [category]
|
|
57
|
+
* )
|
|
58
|
+
*/
|
|
59
|
+
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>, deps?: Array<unknown>): UseLiveInfiniteQueryReturn<TContext>;
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useLiveQuery } from "./useLiveQuery.js";
|
|
2
|
+
import { useLiveInfiniteQuery } from "./useLiveInfiniteQuery.js";
|
|
2
3
|
export * from "@tanstack/db";
|
|
3
4
|
import { createTransaction } from "@tanstack/db";
|
|
4
5
|
export {
|
|
5
6
|
createTransaction,
|
|
7
|
+
useLiveInfiniteQuery,
|
|
6
8
|
useLiveQuery
|
|
7
9
|
};
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useLiveQuery } from './useLiveQuery.js';
|
|
2
|
+
import { Context, InferResultType, InitialQueryBuilder, QueryBuilder } from '@tanstack/db';
|
|
3
|
+
export type UseLiveInfiniteQueryConfig<TContext extends Context> = {
|
|
4
|
+
pageSize?: number;
|
|
5
|
+
initialPageParam?: number;
|
|
6
|
+
getNextPageParam: (lastPage: Array<InferResultType<TContext>[number]>, allPages: Array<Array<InferResultType<TContext>[number]>>, lastPageParam: number, allPageParams: Array<number>) => number | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<ReturnType<typeof useLiveQuery<TContext>>, `data`> & {
|
|
9
|
+
data: InferResultType<TContext>;
|
|
10
|
+
pages: Array<Array<InferResultType<TContext>[number]>>;
|
|
11
|
+
pageParams: Array<number>;
|
|
12
|
+
fetchNextPage: () => void;
|
|
13
|
+
hasNextPage: boolean;
|
|
14
|
+
isFetchingNextPage: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Create an infinite query using a query function with live updates
|
|
18
|
+
*
|
|
19
|
+
* Uses `utils.setWindow()` to dynamically adjust the limit/offset window
|
|
20
|
+
* without recreating the live query collection on each page change.
|
|
21
|
+
*
|
|
22
|
+
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
23
|
+
* @param config - Configuration including pageSize and getNextPageParam
|
|
24
|
+
* @param deps - Array of dependencies that trigger query re-execution when changed
|
|
25
|
+
* @returns Object with pages, data, and pagination controls
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Basic infinite query
|
|
29
|
+
* const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
|
|
30
|
+
* (q) => q
|
|
31
|
+
* .from({ posts: postsCollection })
|
|
32
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc')
|
|
33
|
+
* .select(({ posts }) => ({
|
|
34
|
+
* id: posts.id,
|
|
35
|
+
* title: posts.title
|
|
36
|
+
* })),
|
|
37
|
+
* {
|
|
38
|
+
* pageSize: 20,
|
|
39
|
+
* getNextPageParam: (lastPage, allPages) =>
|
|
40
|
+
* lastPage.length === 20 ? allPages.length : undefined
|
|
41
|
+
* }
|
|
42
|
+
* )
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // With dependencies
|
|
46
|
+
* const { pages, fetchNextPage } = useLiveInfiniteQuery(
|
|
47
|
+
* (q) => q
|
|
48
|
+
* .from({ posts: postsCollection })
|
|
49
|
+
* .where(({ posts }) => eq(posts.category, category))
|
|
50
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc'),
|
|
51
|
+
* {
|
|
52
|
+
* pageSize: 10,
|
|
53
|
+
* getNextPageParam: (lastPage) =>
|
|
54
|
+
* lastPage.length === 10 ? lastPage.length : undefined
|
|
55
|
+
* },
|
|
56
|
+
* [category]
|
|
57
|
+
* )
|
|
58
|
+
*/
|
|
59
|
+
export declare function useLiveInfiniteQuery<TContext extends Context>(queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>, config: UseLiveInfiniteQueryConfig<TContext>, deps?: Array<unknown>): UseLiveInfiniteQueryReturn<TContext>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
|
2
|
+
import { useLiveQuery } from "./useLiveQuery.js";
|
|
3
|
+
function isLiveQueryCollectionUtils(utils) {
|
|
4
|
+
return typeof utils.setWindow === `function`;
|
|
5
|
+
}
|
|
6
|
+
function useLiveInfiniteQuery(queryFn, config, deps = []) {
|
|
7
|
+
const pageSize = config.pageSize || 20;
|
|
8
|
+
const initialPageParam = config.initialPageParam ?? 0;
|
|
9
|
+
const [loadedPageCount, setLoadedPageCount] = useState(1);
|
|
10
|
+
const [isFetchingNextPage, setIsFetchingNextPage] = useState(false);
|
|
11
|
+
const depsKey = JSON.stringify(deps);
|
|
12
|
+
const prevDepsKeyRef = useRef(depsKey);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (prevDepsKeyRef.current !== depsKey) {
|
|
15
|
+
setLoadedPageCount(1);
|
|
16
|
+
prevDepsKeyRef.current = depsKey;
|
|
17
|
+
}
|
|
18
|
+
}, [depsKey]);
|
|
19
|
+
const queryResult = useLiveQuery(
|
|
20
|
+
(q) => queryFn(q).limit(pageSize).offset(0),
|
|
21
|
+
deps
|
|
22
|
+
);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const newLimit = loadedPageCount * pageSize + 1;
|
|
25
|
+
const utils = queryResult.collection.utils;
|
|
26
|
+
if (isLiveQueryCollectionUtils(utils)) {
|
|
27
|
+
const result = utils.setWindow({ offset: 0, limit: newLimit });
|
|
28
|
+
if (result !== true) {
|
|
29
|
+
setIsFetchingNextPage(true);
|
|
30
|
+
result.then(() => {
|
|
31
|
+
setIsFetchingNextPage(false);
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
setIsFetchingNextPage(false);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [loadedPageCount, pageSize, queryResult.collection]);
|
|
38
|
+
const { pages, pageParams, hasNextPage, flatData } = useMemo(() => {
|
|
39
|
+
const dataArray = queryResult.data;
|
|
40
|
+
const totalItemsRequested = loadedPageCount * pageSize;
|
|
41
|
+
const hasMore = dataArray.length > totalItemsRequested;
|
|
42
|
+
const pagesResult = [];
|
|
43
|
+
const pageParamsResult = [];
|
|
44
|
+
for (let i = 0; i < loadedPageCount; i++) {
|
|
45
|
+
const pageData = dataArray.slice(i * pageSize, (i + 1) * pageSize);
|
|
46
|
+
pagesResult.push(pageData);
|
|
47
|
+
pageParamsResult.push(initialPageParam + i);
|
|
48
|
+
}
|
|
49
|
+
const flatDataResult = dataArray.slice(
|
|
50
|
+
0,
|
|
51
|
+
totalItemsRequested
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
pages: pagesResult,
|
|
55
|
+
pageParams: pageParamsResult,
|
|
56
|
+
hasNextPage: hasMore,
|
|
57
|
+
flatData: flatDataResult
|
|
58
|
+
};
|
|
59
|
+
}, [queryResult.data, loadedPageCount, pageSize, initialPageParam]);
|
|
60
|
+
const fetchNextPage = useCallback(() => {
|
|
61
|
+
if (!hasNextPage || isFetchingNextPage) return;
|
|
62
|
+
setLoadedPageCount((prev) => prev + 1);
|
|
63
|
+
}, [hasNextPage, isFetchingNextPage]);
|
|
64
|
+
return {
|
|
65
|
+
...queryResult,
|
|
66
|
+
data: flatData,
|
|
67
|
+
pages,
|
|
68
|
+
pageParams,
|
|
69
|
+
fetchNextPage,
|
|
70
|
+
hasNextPage,
|
|
71
|
+
isFetchingNextPage
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
useLiveInfiniteQuery
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=useLiveInfiniteQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLiveInfiniteQuery.js","sources":["../../src/useLiveInfiniteQuery.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { useLiveQuery } from \"./useLiveQuery\"\nimport type {\n Context,\n InferResultType,\n InitialQueryBuilder,\n LiveQueryCollectionUtils,\n QueryBuilder,\n} from \"@tanstack/db\"\n\n/**\n * Type guard to check if utils object has setWindow method (LiveQueryCollectionUtils)\n */\nfunction isLiveQueryCollectionUtils(\n utils: unknown\n): utils is LiveQueryCollectionUtils {\n return typeof (utils as any).setWindow === `function`\n}\n\nexport type UseLiveInfiniteQueryConfig<TContext extends Context> = {\n pageSize?: number\n initialPageParam?: number\n getNextPageParam: (\n lastPage: Array<InferResultType<TContext>[number]>,\n allPages: Array<Array<InferResultType<TContext>[number]>>,\n lastPageParam: number,\n allPageParams: Array<number>\n ) => number | undefined\n}\n\nexport type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<\n ReturnType<typeof useLiveQuery<TContext>>,\n `data`\n> & {\n data: InferResultType<TContext>\n pages: Array<Array<InferResultType<TContext>[number]>>\n pageParams: Array<number>\n fetchNextPage: () => void\n hasNextPage: boolean\n isFetchingNextPage: boolean\n}\n\n/**\n * Create an infinite query using a query function with live updates\n *\n * Uses `utils.setWindow()` to dynamically adjust the limit/offset window\n * without recreating the live query collection on each page change.\n *\n * @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.\n * @param config - Configuration including pageSize and getNextPageParam\n * @param deps - Array of dependencies that trigger query re-execution when changed\n * @returns Object with pages, data, and pagination controls\n *\n * @example\n * // Basic infinite query\n * const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .orderBy(({ posts }) => posts.createdAt, 'desc')\n * .select(({ posts }) => ({\n * id: posts.id,\n * title: posts.title\n * })),\n * {\n * pageSize: 20,\n * getNextPageParam: (lastPage, allPages) =>\n * lastPage.length === 20 ? allPages.length : undefined\n * }\n * )\n *\n * @example\n * // With dependencies\n * const { pages, fetchNextPage } = useLiveInfiniteQuery(\n * (q) => q\n * .from({ posts: postsCollection })\n * .where(({ posts }) => eq(posts.category, category))\n * .orderBy(({ posts }) => posts.createdAt, 'desc'),\n * {\n * pageSize: 10,\n * getNextPageParam: (lastPage) =>\n * lastPage.length === 10 ? lastPage.length : undefined\n * },\n * [category]\n * )\n */\nexport function useLiveInfiniteQuery<TContext extends Context>(\n queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,\n config: UseLiveInfiniteQueryConfig<TContext>,\n deps: Array<unknown> = []\n): UseLiveInfiniteQueryReturn<TContext> {\n const pageSize = config.pageSize || 20\n const initialPageParam = config.initialPageParam ?? 0\n\n // Track how many pages have been loaded\n const [loadedPageCount, setLoadedPageCount] = useState(1)\n const [isFetchingNextPage, setIsFetchingNextPage] = useState(false)\n\n // Stringify deps for comparison\n const depsKey = JSON.stringify(deps)\n const prevDepsKeyRef = useRef(depsKey)\n\n // Reset page count when dependencies change\n useEffect(() => {\n if (prevDepsKeyRef.current !== depsKey) {\n setLoadedPageCount(1)\n prevDepsKeyRef.current = depsKey\n }\n }, [depsKey])\n\n // Create a live query with initial limit and offset\n // The query function is wrapped to add limit/offset to the query\n const queryResult = useLiveQuery(\n (q) => queryFn(q).limit(pageSize).offset(0),\n deps\n )\n\n // Update the window when loadedPageCount changes\n // We fetch one extra item to peek if there's a next page\n useEffect(() => {\n const newLimit = loadedPageCount * pageSize + 1 // +1 to peek ahead\n const utils = queryResult.collection.utils\n // setWindow is available on live query collections with orderBy\n if (isLiveQueryCollectionUtils(utils)) {\n const result = utils.setWindow({ offset: 0, limit: newLimit })\n // setWindow returns true if data is immediately available, or Promise<void> if loading\n if (result !== true) {\n setIsFetchingNextPage(true)\n result.then(() => {\n setIsFetchingNextPage(false)\n })\n } else {\n setIsFetchingNextPage(false)\n }\n }\n }, [loadedPageCount, pageSize, queryResult.collection])\n\n // Split the data array into pages and determine if there's a next page\n const { pages, pageParams, hasNextPage, flatData } = useMemo(() => {\n const dataArray = queryResult.data as InferResultType<TContext>\n const totalItemsRequested = loadedPageCount * pageSize\n\n // Check if we have more data than requested (the peek ahead item)\n const hasMore = dataArray.length > totalItemsRequested\n\n // Build pages array (without the peek ahead item)\n const pagesResult: Array<Array<InferResultType<TContext>[number]>> = []\n const pageParamsResult: Array<number> = []\n\n for (let i = 0; i < loadedPageCount; i++) {\n const pageData = dataArray.slice(i * pageSize, (i + 1) * pageSize)\n pagesResult.push(pageData)\n pageParamsResult.push(initialPageParam + i)\n }\n\n // Flatten the pages for the data return (without peek ahead item)\n const flatDataResult = dataArray.slice(\n 0,\n totalItemsRequested\n ) as InferResultType<TContext>\n\n return {\n pages: pagesResult,\n pageParams: pageParamsResult,\n hasNextPage: hasMore,\n flatData: flatDataResult,\n }\n }, [queryResult.data, loadedPageCount, pageSize, initialPageParam])\n\n // Fetch next page\n const fetchNextPage = useCallback(() => {\n if (!hasNextPage || isFetchingNextPage) return\n\n setLoadedPageCount((prev) => prev + 1)\n }, [hasNextPage, isFetchingNextPage])\n\n return {\n ...queryResult,\n data: flatData,\n pages,\n pageParams,\n fetchNextPage,\n hasNextPage,\n isFetchingNextPage,\n }\n}\n"],"names":[],"mappings":";;AAaA,SAAS,2BACP,OACmC;AACnC,SAAO,OAAQ,MAAc,cAAc;AAC7C;AAoEO,SAAS,qBACd,SACA,QACA,OAAuB,CAAA,GACe;AACtC,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,mBAAmB,OAAO,oBAAoB;AAGpD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,CAAC;AACxD,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,SAAS,KAAK;AAGlE,QAAM,UAAU,KAAK,UAAU,IAAI;AACnC,QAAM,iBAAiB,OAAO,OAAO;AAGrC,YAAU,MAAM;AACd,QAAI,eAAe,YAAY,SAAS;AACtC,yBAAmB,CAAC;AACpB,qBAAe,UAAU;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAIZ,QAAM,cAAc;AAAA,IAClB,CAAC,MAAM,QAAQ,CAAC,EAAE,MAAM,QAAQ,EAAE,OAAO,CAAC;AAAA,IAC1C;AAAA,EAAA;AAKF,YAAU,MAAM;AACd,UAAM,WAAW,kBAAkB,WAAW;AAC9C,UAAM,QAAQ,YAAY,WAAW;AAErC,QAAI,2BAA2B,KAAK,GAAG;AACrC,YAAM,SAAS,MAAM,UAAU,EAAE,QAAQ,GAAG,OAAO,UAAU;AAE7D,UAAI,WAAW,MAAM;AACnB,8BAAsB,IAAI;AAC1B,eAAO,KAAK,MAAM;AAChB,gCAAsB,KAAK;AAAA,QAC7B,CAAC;AAAA,MACH,OAAO;AACL,8BAAsB,KAAK;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,UAAU,YAAY,UAAU,CAAC;AAGtD,QAAM,EAAE,OAAO,YAAY,aAAa,SAAA,IAAa,QAAQ,MAAM;AACjE,UAAM,YAAY,YAAY;AAC9B,UAAM,sBAAsB,kBAAkB;AAG9C,UAAM,UAAU,UAAU,SAAS;AAGnC,UAAM,cAA+D,CAAA;AACrE,UAAM,mBAAkC,CAAA;AAExC,aAAS,IAAI,GAAG,IAAI,iBAAiB,KAAK;AACxC,YAAM,WAAW,UAAU,MAAM,IAAI,WAAW,IAAI,KAAK,QAAQ;AACjE,kBAAY,KAAK,QAAQ;AACzB,uBAAiB,KAAK,mBAAmB,CAAC;AAAA,IAC5C;AAGA,UAAM,iBAAiB,UAAU;AAAA,MAC/B;AAAA,MACA;AAAA,IAAA;AAGF,WAAO;AAAA,MACL,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,UAAU;AAAA,IAAA;AAAA,EAEd,GAAG,CAAC,YAAY,MAAM,iBAAiB,UAAU,gBAAgB,CAAC;AAGlE,QAAM,gBAAgB,YAAY,MAAM;AACtC,QAAI,CAAC,eAAe,mBAAoB;AAExC,uBAAmB,CAAC,SAAS,OAAO,CAAC;AAAA,EACvC,GAAG,CAAC,aAAa,kBAAkB,CAAC;AAEpC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-db",
|
|
3
3
|
"description": "React integration for @tanstack/db",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.32",
|
|
5
5
|
"author": "Kyle Mathews",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"use-sync-external-store": "^1.6.0",
|
|
20
|
-
"@tanstack/db": "0.4.
|
|
20
|
+
"@tanstack/db": "0.4.10"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@electric-sql/client": "1.0.14",
|
|
24
24
|
"@testing-library/react": "^16.3.0",
|
|
25
|
-
"@types/react": "^19.2.
|
|
26
|
-
"@types/react-dom": "^19.2.
|
|
25
|
+
"@types/react": "^19.2.2",
|
|
26
|
+
"@types/react-dom": "^19.2.1",
|
|
27
27
|
"@types/use-sync-external-store": "^1.5.0",
|
|
28
28
|
"@vitest/coverage-istanbul": "^3.2.4",
|
|
29
29
|
"react": "^19.2.0",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
2
|
+
import { useLiveQuery } from "./useLiveQuery"
|
|
3
|
+
import type {
|
|
4
|
+
Context,
|
|
5
|
+
InferResultType,
|
|
6
|
+
InitialQueryBuilder,
|
|
7
|
+
LiveQueryCollectionUtils,
|
|
8
|
+
QueryBuilder,
|
|
9
|
+
} from "@tanstack/db"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Type guard to check if utils object has setWindow method (LiveQueryCollectionUtils)
|
|
13
|
+
*/
|
|
14
|
+
function isLiveQueryCollectionUtils(
|
|
15
|
+
utils: unknown
|
|
16
|
+
): utils is LiveQueryCollectionUtils {
|
|
17
|
+
return typeof (utils as any).setWindow === `function`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type UseLiveInfiniteQueryConfig<TContext extends Context> = {
|
|
21
|
+
pageSize?: number
|
|
22
|
+
initialPageParam?: number
|
|
23
|
+
getNextPageParam: (
|
|
24
|
+
lastPage: Array<InferResultType<TContext>[number]>,
|
|
25
|
+
allPages: Array<Array<InferResultType<TContext>[number]>>,
|
|
26
|
+
lastPageParam: number,
|
|
27
|
+
allPageParams: Array<number>
|
|
28
|
+
) => number | undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type UseLiveInfiniteQueryReturn<TContext extends Context> = Omit<
|
|
32
|
+
ReturnType<typeof useLiveQuery<TContext>>,
|
|
33
|
+
`data`
|
|
34
|
+
> & {
|
|
35
|
+
data: InferResultType<TContext>
|
|
36
|
+
pages: Array<Array<InferResultType<TContext>[number]>>
|
|
37
|
+
pageParams: Array<number>
|
|
38
|
+
fetchNextPage: () => void
|
|
39
|
+
hasNextPage: boolean
|
|
40
|
+
isFetchingNextPage: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create an infinite query using a query function with live updates
|
|
45
|
+
*
|
|
46
|
+
* Uses `utils.setWindow()` to dynamically adjust the limit/offset window
|
|
47
|
+
* without recreating the live query collection on each page change.
|
|
48
|
+
*
|
|
49
|
+
* @param queryFn - Query function that defines what data to fetch. Must include `.orderBy()` for setWindow to work.
|
|
50
|
+
* @param config - Configuration including pageSize and getNextPageParam
|
|
51
|
+
* @param deps - Array of dependencies that trigger query re-execution when changed
|
|
52
|
+
* @returns Object with pages, data, and pagination controls
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Basic infinite query
|
|
56
|
+
* const { data, pages, fetchNextPage, hasNextPage } = useLiveInfiniteQuery(
|
|
57
|
+
* (q) => q
|
|
58
|
+
* .from({ posts: postsCollection })
|
|
59
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc')
|
|
60
|
+
* .select(({ posts }) => ({
|
|
61
|
+
* id: posts.id,
|
|
62
|
+
* title: posts.title
|
|
63
|
+
* })),
|
|
64
|
+
* {
|
|
65
|
+
* pageSize: 20,
|
|
66
|
+
* getNextPageParam: (lastPage, allPages) =>
|
|
67
|
+
* lastPage.length === 20 ? allPages.length : undefined
|
|
68
|
+
* }
|
|
69
|
+
* )
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // With dependencies
|
|
73
|
+
* const { pages, fetchNextPage } = useLiveInfiniteQuery(
|
|
74
|
+
* (q) => q
|
|
75
|
+
* .from({ posts: postsCollection })
|
|
76
|
+
* .where(({ posts }) => eq(posts.category, category))
|
|
77
|
+
* .orderBy(({ posts }) => posts.createdAt, 'desc'),
|
|
78
|
+
* {
|
|
79
|
+
* pageSize: 10,
|
|
80
|
+
* getNextPageParam: (lastPage) =>
|
|
81
|
+
* lastPage.length === 10 ? lastPage.length : undefined
|
|
82
|
+
* },
|
|
83
|
+
* [category]
|
|
84
|
+
* )
|
|
85
|
+
*/
|
|
86
|
+
export function useLiveInfiniteQuery<TContext extends Context>(
|
|
87
|
+
queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
|
|
88
|
+
config: UseLiveInfiniteQueryConfig<TContext>,
|
|
89
|
+
deps: Array<unknown> = []
|
|
90
|
+
): UseLiveInfiniteQueryReturn<TContext> {
|
|
91
|
+
const pageSize = config.pageSize || 20
|
|
92
|
+
const initialPageParam = config.initialPageParam ?? 0
|
|
93
|
+
|
|
94
|
+
// Track how many pages have been loaded
|
|
95
|
+
const [loadedPageCount, setLoadedPageCount] = useState(1)
|
|
96
|
+
const [isFetchingNextPage, setIsFetchingNextPage] = useState(false)
|
|
97
|
+
|
|
98
|
+
// Stringify deps for comparison
|
|
99
|
+
const depsKey = JSON.stringify(deps)
|
|
100
|
+
const prevDepsKeyRef = useRef(depsKey)
|
|
101
|
+
|
|
102
|
+
// Reset page count when dependencies change
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (prevDepsKeyRef.current !== depsKey) {
|
|
105
|
+
setLoadedPageCount(1)
|
|
106
|
+
prevDepsKeyRef.current = depsKey
|
|
107
|
+
}
|
|
108
|
+
}, [depsKey])
|
|
109
|
+
|
|
110
|
+
// Create a live query with initial limit and offset
|
|
111
|
+
// The query function is wrapped to add limit/offset to the query
|
|
112
|
+
const queryResult = useLiveQuery(
|
|
113
|
+
(q) => queryFn(q).limit(pageSize).offset(0),
|
|
114
|
+
deps
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Update the window when loadedPageCount changes
|
|
118
|
+
// We fetch one extra item to peek if there's a next page
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const newLimit = loadedPageCount * pageSize + 1 // +1 to peek ahead
|
|
121
|
+
const utils = queryResult.collection.utils
|
|
122
|
+
// setWindow is available on live query collections with orderBy
|
|
123
|
+
if (isLiveQueryCollectionUtils(utils)) {
|
|
124
|
+
const result = utils.setWindow({ offset: 0, limit: newLimit })
|
|
125
|
+
// setWindow returns true if data is immediately available, or Promise<void> if loading
|
|
126
|
+
if (result !== true) {
|
|
127
|
+
setIsFetchingNextPage(true)
|
|
128
|
+
result.then(() => {
|
|
129
|
+
setIsFetchingNextPage(false)
|
|
130
|
+
})
|
|
131
|
+
} else {
|
|
132
|
+
setIsFetchingNextPage(false)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}, [loadedPageCount, pageSize, queryResult.collection])
|
|
136
|
+
|
|
137
|
+
// Split the data array into pages and determine if there's a next page
|
|
138
|
+
const { pages, pageParams, hasNextPage, flatData } = useMemo(() => {
|
|
139
|
+
const dataArray = queryResult.data as InferResultType<TContext>
|
|
140
|
+
const totalItemsRequested = loadedPageCount * pageSize
|
|
141
|
+
|
|
142
|
+
// Check if we have more data than requested (the peek ahead item)
|
|
143
|
+
const hasMore = dataArray.length > totalItemsRequested
|
|
144
|
+
|
|
145
|
+
// Build pages array (without the peek ahead item)
|
|
146
|
+
const pagesResult: Array<Array<InferResultType<TContext>[number]>> = []
|
|
147
|
+
const pageParamsResult: Array<number> = []
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < loadedPageCount; i++) {
|
|
150
|
+
const pageData = dataArray.slice(i * pageSize, (i + 1) * pageSize)
|
|
151
|
+
pagesResult.push(pageData)
|
|
152
|
+
pageParamsResult.push(initialPageParam + i)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Flatten the pages for the data return (without peek ahead item)
|
|
156
|
+
const flatDataResult = dataArray.slice(
|
|
157
|
+
0,
|
|
158
|
+
totalItemsRequested
|
|
159
|
+
) as InferResultType<TContext>
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
pages: pagesResult,
|
|
163
|
+
pageParams: pageParamsResult,
|
|
164
|
+
hasNextPage: hasMore,
|
|
165
|
+
flatData: flatDataResult,
|
|
166
|
+
}
|
|
167
|
+
}, [queryResult.data, loadedPageCount, pageSize, initialPageParam])
|
|
168
|
+
|
|
169
|
+
// Fetch next page
|
|
170
|
+
const fetchNextPage = useCallback(() => {
|
|
171
|
+
if (!hasNextPage || isFetchingNextPage) return
|
|
172
|
+
|
|
173
|
+
setLoadedPageCount((prev) => prev + 1)
|
|
174
|
+
}, [hasNextPage, isFetchingNextPage])
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
...queryResult,
|
|
178
|
+
data: flatData,
|
|
179
|
+
pages,
|
|
180
|
+
pageParams,
|
|
181
|
+
fetchNextPage,
|
|
182
|
+
hasNextPage,
|
|
183
|
+
isFetchingNextPage,
|
|
184
|
+
}
|
|
185
|
+
}
|