Skip to content

Commit 0380229

Browse files
authored
feat: useQueriesOnce & useQueriesDataOnce (#239)
1 parent f9c30ea commit 0380229

20 files changed

+229
-39
lines changed

docs/firestore.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Returns:
103103
Returns and updates a QuerySnapshot of multiple Firestore queries
104104

105105
```javascript
106-
const [querySnap, loading, error] = useQueries(queries, options);
106+
const results = useQueries(queries, options);
107107
```
108108

109109
Params:
@@ -123,7 +123,7 @@ Returns:
123123
Returns and updates a the document data of multiple Firestore queries
124124

125125
```javascript
126-
const [querySnap, loading, error] = useQueriesData(query, options);
126+
const results = useQueriesData(query, options);
127127
```
128128

129129
Params:
@@ -138,6 +138,46 @@ Returns:
138138
- `loading` :`true` while fetching the query; `false` if the query was fetched successfully or an error occurred
139139
- `error`: `undefined` if no error occurred
140140

141+
## useQueriesDataOnce
142+
143+
Returns the data of multiple Firestore queries
144+
145+
```javascript
146+
const results = useQueriesDataOnce(queries, options);
147+
```
148+
149+
Params:
150+
151+
- `queries`: Firestore queries that will be fetched
152+
- `options`: Options to configure how the queries are fetched
153+
154+
Returns:
155+
156+
- Array with tuple for each query::
157+
- `value`: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
158+
- `loading`: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
159+
- `error`: `undefined` if no error occurred
160+
161+
## useQueriesOnce
162+
163+
Returns the QuerySnapshots of multiple Firestore queries
164+
165+
```javascript
166+
const results = useQueriesOnce(queries, options);
167+
```
168+
169+
Params:
170+
171+
- `queries`: Firestore queries that will be fetched
172+
- `options`: Options to configure how the queries are fetched
173+
174+
Returns:
175+
176+
- Array with tuple for each query::
177+
- `value`: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
178+
- `loading`: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
179+
- `error`: `undefined` if no error occurred
180+
141181
## useQuery
142182

143183
Returns and updates a QuerySnapshot of a Firestore Query

src/database/useObjectOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DataSnapshot, get, Query } from "firebase/database";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/index.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { isQueryEqual } from "./internal.js";
66

77
export type UseObjectOnceResult = ValueHookResult<DataSnapshot, Error>;
@@ -16,5 +16,5 @@ export type UseObjectOnceResult = ValueHookResult<DataSnapshot, Error>;
1616
*/
1717
export function useObjectOnce(query: Query | undefined | null): UseObjectOnceResult {
1818
const getData = useCallback((stableQuery: Query) => get(stableQuery), []);
19-
return useOnce(query ?? undefined, getData, isQueryEqual);
19+
return useGet(query ?? undefined, getData, isQueryEqual);
2020
}

src/database/useObjectValueOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DataSnapshot, get, Query } from "firebase/database";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/index.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { defaultConverter, isQueryEqual } from "./internal.js";
66

77
export type UseObjectValueOnceResult<Value = unknown> = ValueHookResult<Value, Error>;
@@ -36,5 +36,5 @@ export function useObjectValueOnce<Value = unknown>(
3636
// eslint-disable-next-line react-hooks/exhaustive-deps
3737
}, []);
3838

39-
return useOnce(query ?? undefined, getData, isQueryEqual);
39+
return useGet(query ?? undefined, getData, isQueryEqual);
4040
}

src/firestore/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from "./useDocumentDataOnce.js";
66
export * from "./useDocumentOnce.js";
77
export * from "./useQueries.js";
88
export * from "./useQueriesData.js";
9+
export * from "./useQueriesDataOnce.js";
910
export * from "./useQuery.js";
1011
export * from "./useQueryData.js";
1112
export * from "./useQueryDataOnce.js";

src/firestore/useCountFromServer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FirestoreError, Query, getCountFromServer } from "firebase/firestore";
22
import type { ValueHookResult } from "../common/types.js";
3-
import { useOnce } from "../internal/useOnce.js";
3+
import { useGet } from "../internal/useGet.js";
44
import { isQueryEqual } from "./internal.js";
55

66
export type UseCountFromServerResult = ValueHookResult<number, FirestoreError>;
@@ -23,5 +23,5 @@ async function getData(stableQuery: Query<unknown>): Promise<number> {
2323
* error: `undefined` if no error occurred
2424
*/
2525
export function useCountFromServer(query: Query<unknown> | undefined | null): UseCountFromServerResult {
26-
return useOnce(query ?? undefined, getData, isQueryEqual);
26+
return useGet(query ?? undefined, getData, isQueryEqual);
2727
}

src/firestore/useDocumentDataOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DocumentData, DocumentReference, FirestoreError, SnapshotOptions } from "firebase/firestore";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/types.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { getDocFromSource, isDocRefEqual } from "./internal.js";
66
import type { Source } from "./types.js";
77

@@ -40,5 +40,5 @@ export function useDocumentDataOnce<Value extends DocumentData = DocumentData>(
4040
[serverTimestamps, source],
4141
);
4242

43-
return useOnce(reference ?? undefined, getData, isDocRefEqual);
43+
return useGet(reference ?? undefined, getData, isDocRefEqual);
4444
}

src/firestore/useDocumentOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DocumentData, DocumentReference, DocumentSnapshot, FirestoreError } from "firebase/firestore";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/types.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { getDocFromSource, isDocRefEqual } from "./internal.js";
66
import type { Source } from "./types.js";
77

@@ -35,5 +35,5 @@ export function useDocumentOnce<Value extends DocumentData = DocumentData>(
3535

3636
const getData = useCallback((stableRef: DocumentReference<Value>) => getDocFromSource(stableRef, source), [source]);
3737

38-
return useOnce(reference ?? undefined, getData, isDocRefEqual);
38+
return useGet(reference ?? undefined, getData, isDocRefEqual);
3939
}

src/firestore/useQueriesDataOnce.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
2+
import { useCallback } from "react";
3+
import type { ValueHookResult } from "../common/types.js";
4+
import { useMultiGet } from "../internal/useMultiGet.js";
5+
import { getDocsFromSource, isQueryEqual } from "./internal.js";
6+
import type { Source } from "./types.js";
7+
8+
export type UseQueriesDataOnceResult<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>> = {
9+
[Index in keyof Values]: ValueHookResult<Values[Index], FirestoreError>;
10+
} & { length: Values["length"] };
11+
12+
/**
13+
* Options to configure the subscription
14+
*/
15+
export interface UseQueriesDataOnceOptions {
16+
source?: Source;
17+
snapshotOptions?: SnapshotOptions;
18+
}
19+
20+
/**
21+
* Returns the data of multiple Firestore queries. Does not update the data once initially fetched
22+
* @template Values Tuple of types of the collection data
23+
* @param queries Firestore queries that will be fetched
24+
* @param options Options to configure how the queries are fetched
25+
* @returns Array with tuple for each query:
26+
* value: Query data; `undefined` if query is currently being fetched, or an error occurred
27+
* loading: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
28+
* error: `undefined` if no error occurred
29+
*/
30+
export function useQueriesDataOnce<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
31+
queries: { [Index in keyof Values]: Query<Values[Index]> },
32+
options?: UseQueriesDataOnceOptions,
33+
): UseQueriesDataOnceResult<Values> {
34+
const { source = "default", snapshotOptions } = options ?? {};
35+
const { serverTimestamps } = snapshotOptions ?? {};
36+
37+
const getData = useCallback(
38+
async (stableQuery: Query<Values[number]>) => {
39+
const snap = await getDocsFromSource(stableQuery, source);
40+
return snap.docs.map((doc) => doc.data({ serverTimestamps }));
41+
},
42+
[source, serverTimestamps],
43+
);
44+
45+
// @ts-expect-error `useMultiGet` assumes a single value type
46+
return useMultiGet(queries, getData, isQueryEqual);
47+
}

src/firestore/useQueriesOnce.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
2+
import { useCallback } from "react";
3+
import type { ValueHookResult } from "../common/types.js";
4+
import { useMultiGet } from "../internal/useMultiGet.js";
5+
import { getDocsFromSource, isQueryEqual } from "./internal.js";
6+
import type { Source } from "./types.js";
7+
8+
export type UseQueriesOnceResult<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>> = {
9+
[Index in keyof Values]: ValueHookResult<Values[Index], FirestoreError>;
10+
} & { length: Values["length"] };
11+
12+
/**
13+
* Options to configure the subscription
14+
*/
15+
export interface UseQueriesOnceOptions {
16+
source?: Source;
17+
snapshotOptions?: SnapshotOptions;
18+
}
19+
20+
/**
21+
* Returns the QuerySnapshot of multiple Firestore queries. Does not update the data once initially fetched
22+
* @template Values Tuple of types of the collection data
23+
* @param queries Firestore queries that will be fetched
24+
* @param options Options to configure how the queries are fetched
25+
* @returns Array with tuple for each query:
26+
* value: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
27+
* loading: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
28+
* error: `undefined` if no error occurred
29+
*/
30+
export function useQueriesOnce<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
31+
queries: { [Index in keyof Values]: Query<Values[Index]> },
32+
options?: UseQueriesOnceOptions,
33+
): UseQueriesOnceResult<Values> {
34+
const { source = "default" } = options ?? {};
35+
36+
const getData = useCallback(
37+
async (stableQuery: Query<Values[number]>) => getDocsFromSource(stableQuery, source),
38+
[source],
39+
);
40+
41+
// @ts-expect-error `useMultiGet` assumes a single value type
42+
return useMultiGet(queries, getData, isQueryEqual);
43+
}

src/firestore/useQueryDataOnce.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/types.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { getDocsFromSource, isQueryEqual } from "./internal.js";
66
import type { Source } from "./types.js";
77

@@ -40,5 +40,5 @@ export function useQueryDataOnce<Value extends DocumentData = DocumentData>(
4040
[serverTimestamps, source],
4141
);
4242

43-
return useOnce(query ?? undefined, getData, isQueryEqual);
43+
return useGet(query ?? undefined, getData, isQueryEqual);
4444
}

src/firestore/useQueryOnce.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DocumentData, FirestoreError, Query, QuerySnapshot } from "firebase/firestore";
22
import { useCallback } from "react";
33
import type { ValueHookResult } from "../common/types.js";
4-
import { useOnce } from "../internal/useOnce.js";
4+
import { useGet } from "../internal/useGet.js";
55
import { getDocsFromSource, isQueryEqual } from "./internal.js";
66
import type { Source } from "./types.js";
77

@@ -18,7 +18,7 @@ export interface UseQueryOnceOptions {
1818
}
1919

2020
/**
21-
* Returns the QuerySnapshot of a Firestore Query. Does not update the QuerySnapshot once initially fetched
21+
* Returns the QuerySnapshot of a Firestore query. Does not update the QuerySnapshot once initially fetched
2222
* @template Value Type of the collection data
2323
* @param query Firestore query that will be fetched
2424
* @param options Options to configure how the query is fetched
@@ -35,5 +35,5 @@ export function useQueryOnce<Value extends DocumentData = DocumentData>(
3535

3636
const getData = useCallback(async (stableQuery: Query<Value>) => getDocsFromSource(stableQuery, source), [source]);
3737

38-
return useOnce(query ?? undefined, getData, isQueryEqual);
38+
return useGet(query ?? undefined, getData, isQueryEqual);
3939
}

src/internal/useOnce.spec.ts renamed to src/internal/useGet.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { renderHook, waitFor } from "@testing-library/react";
22
import { newPromise, newSymbol } from "../__testfixtures__";
3-
import { useOnce } from "./useOnce";
3+
import { useGet } from "./useGet";
44
import { it, expect, beforeEach, describe, vi } from "vitest";
55

66
const result1 = newSymbol("Result 1");
@@ -24,12 +24,12 @@ beforeEach(() => {
2424
describe("initial state", () => {
2525
it("defined reference", () => {
2626
getData.mockReturnValue(new Promise(() => {}));
27-
const { result } = renderHook(() => useOnce(refA1, getData, isEqual));
27+
const { result } = renderHook(() => useGet(refA1, getData, isEqual));
2828
expect(result.current).toStrictEqual([undefined, true, undefined]);
2929
});
3030

3131
it("undefined reference", () => {
32-
const { result } = renderHook(() => useOnce(undefined, getData, isEqual));
32+
const { result } = renderHook(() => useGet(undefined, getData, isEqual));
3333
expect(result.current).toStrictEqual([undefined, false, undefined]);
3434
});
3535
});
@@ -39,7 +39,7 @@ describe("initial load", () => {
3939
const { promise, resolve } = newPromise<string>();
4040
getData.mockReturnValue(promise);
4141

42-
const { result } = renderHook(() => useOnce(refA1, getData, isEqual));
42+
const { result } = renderHook(() => useGet(refA1, getData, isEqual));
4343
expect(result.current).toStrictEqual([undefined, true, undefined]);
4444
resolve(result1);
4545
await waitFor(() => expect(result.current).toStrictEqual([result1, false, undefined]));
@@ -49,7 +49,7 @@ describe("initial load", () => {
4949
const { promise, reject } = newPromise<string>();
5050
getData.mockReturnValue(promise);
5151

52-
const { result } = renderHook(() => useOnce(refA1, getData, isEqual));
52+
const { result } = renderHook(() => useGet(refA1, getData, isEqual));
5353
expect(result.current).toStrictEqual([undefined, true, undefined]);
5454
reject(error);
5555
await waitFor(() => expect(result.current).toStrictEqual([undefined, false, error]));
@@ -61,7 +61,7 @@ describe("when ref changes", () => {
6161
it("should not update success result", async () => {
6262
getData.mockResolvedValueOnce(result1);
6363

64-
const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
64+
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
6565
initialProps: { ref: refA1 },
6666
});
6767

@@ -77,7 +77,7 @@ describe("when ref changes", () => {
7777
it("should not update error result", async () => {
7878
getData.mockRejectedValueOnce(error);
7979

80-
const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
80+
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
8181
initialProps: { ref: refA1 },
8282
});
8383

@@ -95,7 +95,7 @@ describe("when ref changes", () => {
9595
it("should update success result", async () => {
9696
getData.mockResolvedValueOnce(result1).mockResolvedValueOnce(result2);
9797

98-
const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
98+
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
9999
initialProps: { ref: refA1 },
100100
});
101101

@@ -111,7 +111,7 @@ describe("when ref changes", () => {
111111
it("should update error result", async () => {
112112
getData.mockRejectedValueOnce(error).mockResolvedValueOnce(result2);
113113

114-
const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
114+
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
115115
initialProps: { ref: refA1 },
116116
});
117117

src/internal/useOnce.ts renamed to src/internal/useGet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useStableValue } from "./useStableValue.js";
77
/**
88
* @internal
99
*/
10-
export function useOnce<Value, Error, Reference>(
10+
export function useGet<Value, Error, Reference>(
1111
reference: Reference | undefined,
1212
getData: (ref: Reference) => Promise<Value>,
1313
isEqual: (a: Reference | undefined, b: Reference | undefined) => boolean,

0 commit comments

Comments
 (0)