Skip to content

Commit

Permalink
feat: useQueriesOnce & useQueriesDataOnce (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
andipaetzold committed Sep 15, 2023
1 parent f9c30ea commit 0380229
Show file tree
Hide file tree
Showing 20 changed files with 229 additions and 39 deletions.
44 changes: 42 additions & 2 deletions docs/firestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Returns:
Returns and updates a QuerySnapshot of multiple Firestore queries

```javascript
const [querySnap, loading, error] = useQueries(queries, options);
const results = useQueries(queries, options);
```

Params:
Expand All @@ -123,7 +123,7 @@ Returns:
Returns and updates a the document data of multiple Firestore queries

```javascript
const [querySnap, loading, error] = useQueriesData(query, options);
const results = useQueriesData(query, options);
```

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

## useQueriesDataOnce

Returns the data of multiple Firestore queries

```javascript
const results = useQueriesDataOnce(queries, options);
```

Params:

- `queries`: Firestore queries that will be fetched
- `options`: Options to configure how the queries are fetched

Returns:

- Array with tuple for each query::
- `value`: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
- `loading`: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
- `error`: `undefined` if no error occurred

## useQueriesOnce

Returns the QuerySnapshots of multiple Firestore queries

```javascript
const results = useQueriesOnce(queries, options);
```

Params:

- `queries`: Firestore queries that will be fetched
- `options`: Options to configure how the queries are fetched

Returns:

- Array with tuple for each query::
- `value`: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
- `loading`: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
- `error`: `undefined` if no error occurred

## useQuery

Returns and updates a QuerySnapshot of a Firestore Query
Expand Down
4 changes: 2 additions & 2 deletions src/database/useObjectOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DataSnapshot, get, Query } from "firebase/database";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/index.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { isQueryEqual } from "./internal.js";

export type UseObjectOnceResult = ValueHookResult<DataSnapshot, Error>;
Expand All @@ -16,5 +16,5 @@ export type UseObjectOnceResult = ValueHookResult<DataSnapshot, Error>;
*/
export function useObjectOnce(query: Query | undefined | null): UseObjectOnceResult {
const getData = useCallback((stableQuery: Query) => get(stableQuery), []);
return useOnce(query ?? undefined, getData, isQueryEqual);
return useGet(query ?? undefined, getData, isQueryEqual);
}
4 changes: 2 additions & 2 deletions src/database/useObjectValueOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DataSnapshot, get, Query } from "firebase/database";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/index.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { defaultConverter, isQueryEqual } from "./internal.js";

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

return useOnce(query ?? undefined, getData, isQueryEqual);
return useGet(query ?? undefined, getData, isQueryEqual);
}
1 change: 1 addition & 0 deletions src/firestore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./useDocumentDataOnce.js";
export * from "./useDocumentOnce.js";
export * from "./useQueries.js";
export * from "./useQueriesData.js";
export * from "./useQueriesDataOnce.js";
export * from "./useQuery.js";
export * from "./useQueryData.js";
export * from "./useQueryDataOnce.js";
Expand Down
4 changes: 2 additions & 2 deletions src/firestore/useCountFromServer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FirestoreError, Query, getCountFromServer } from "firebase/firestore";
import type { ValueHookResult } from "../common/types.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { isQueryEqual } from "./internal.js";

export type UseCountFromServerResult = ValueHookResult<number, FirestoreError>;
Expand All @@ -23,5 +23,5 @@ async function getData(stableQuery: Query<unknown>): Promise<number> {
* error: `undefined` if no error occurred
*/
export function useCountFromServer(query: Query<unknown> | undefined | null): UseCountFromServerResult {
return useOnce(query ?? undefined, getData, isQueryEqual);
return useGet(query ?? undefined, getData, isQueryEqual);
}
4 changes: 2 additions & 2 deletions src/firestore/useDocumentDataOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DocumentData, DocumentReference, FirestoreError, SnapshotOptions } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { getDocFromSource, isDocRefEqual } from "./internal.js";
import type { Source } from "./types.js";

Expand Down Expand Up @@ -40,5 +40,5 @@ export function useDocumentDataOnce<Value extends DocumentData = DocumentData>(
[serverTimestamps, source],
);

return useOnce(reference ?? undefined, getData, isDocRefEqual);
return useGet(reference ?? undefined, getData, isDocRefEqual);
}
4 changes: 2 additions & 2 deletions src/firestore/useDocumentOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DocumentData, DocumentReference, DocumentSnapshot, FirestoreError } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { getDocFromSource, isDocRefEqual } from "./internal.js";
import type { Source } from "./types.js";

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

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

return useOnce(reference ?? undefined, getData, isDocRefEqual);
return useGet(reference ?? undefined, getData, isDocRefEqual);
}
47 changes: 47 additions & 0 deletions src/firestore/useQueriesDataOnce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useMultiGet } from "../internal/useMultiGet.js";
import { getDocsFromSource, isQueryEqual } from "./internal.js";
import type { Source } from "./types.js";

export type UseQueriesDataOnceResult<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>> = {
[Index in keyof Values]: ValueHookResult<Values[Index], FirestoreError>;
} & { length: Values["length"] };

/**
* Options to configure the subscription
*/
export interface UseQueriesDataOnceOptions {
source?: Source;
snapshotOptions?: SnapshotOptions;
}

/**
* Returns the data of multiple Firestore queries. Does not update the data once initially fetched
* @template Values Tuple of types of the collection data
* @param queries Firestore queries that will be fetched
* @param options Options to configure how the queries are fetched
* @returns Array with tuple for each query:
* value: Query data; `undefined` if query is currently being fetched, or an error occurred
* loading: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
* error: `undefined` if no error occurred
*/
export function useQueriesDataOnce<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
queries: { [Index in keyof Values]: Query<Values[Index]> },
options?: UseQueriesDataOnceOptions,
): UseQueriesDataOnceResult<Values> {
const { source = "default", snapshotOptions } = options ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const getData = useCallback(
async (stableQuery: Query<Values[number]>) => {
const snap = await getDocsFromSource(stableQuery, source);
return snap.docs.map((doc) => doc.data({ serverTimestamps }));
},
[source, serverTimestamps],
);

// @ts-expect-error `useMultiGet` assumes a single value type
return useMultiGet(queries, getData, isQueryEqual);
}
43 changes: 43 additions & 0 deletions src/firestore/useQueriesOnce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useMultiGet } from "../internal/useMultiGet.js";
import { getDocsFromSource, isQueryEqual } from "./internal.js";
import type { Source } from "./types.js";

export type UseQueriesOnceResult<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>> = {
[Index in keyof Values]: ValueHookResult<Values[Index], FirestoreError>;
} & { length: Values["length"] };

/**
* Options to configure the subscription
*/
export interface UseQueriesOnceOptions {
source?: Source;
snapshotOptions?: SnapshotOptions;
}

/**
* Returns the QuerySnapshot of multiple Firestore queries. Does not update the data once initially fetched
* @template Values Tuple of types of the collection data
* @param queries Firestore queries that will be fetched
* @param options Options to configure how the queries are fetched
* @returns Array with tuple for each query:
* value: QuerySnapshot; `undefined` if query is currently being fetched, or an error occurred
* loading: `true` while fetching the query; `false` if the query was fetched successfully or an error occurred
* error: `undefined` if no error occurred
*/
export function useQueriesOnce<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
queries: { [Index in keyof Values]: Query<Values[Index]> },
options?: UseQueriesOnceOptions,
): UseQueriesOnceResult<Values> {
const { source = "default" } = options ?? {};

const getData = useCallback(
async (stableQuery: Query<Values[number]>) => getDocsFromSource(stableQuery, source),
[source],
);

// @ts-expect-error `useMultiGet` assumes a single value type
return useMultiGet(queries, getData, isQueryEqual);
}
4 changes: 2 additions & 2 deletions src/firestore/useQueryDataOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DocumentData, FirestoreError, Query, SnapshotOptions } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { getDocsFromSource, isQueryEqual } from "./internal.js";
import type { Source } from "./types.js";

Expand Down Expand Up @@ -40,5 +40,5 @@ export function useQueryDataOnce<Value extends DocumentData = DocumentData>(
[serverTimestamps, source],
);

return useOnce(query ?? undefined, getData, isQueryEqual);
return useGet(query ?? undefined, getData, isQueryEqual);
}
6 changes: 3 additions & 3 deletions src/firestore/useQueryOnce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DocumentData, FirestoreError, Query, QuerySnapshot } from "firebase/firestore";
import { useCallback } from "react";
import type { ValueHookResult } from "../common/types.js";
import { useOnce } from "../internal/useOnce.js";
import { useGet } from "../internal/useGet.js";
import { getDocsFromSource, isQueryEqual } from "./internal.js";
import type { Source } from "./types.js";

Expand All @@ -18,7 +18,7 @@ export interface UseQueryOnceOptions {
}

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

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

return useOnce(query ?? undefined, getData, isQueryEqual);
return useGet(query ?? undefined, getData, isQueryEqual);
}
18 changes: 9 additions & 9 deletions src/internal/useOnce.spec.ts → src/internal/useGet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { renderHook, waitFor } from "@testing-library/react";
import { newPromise, newSymbol } from "../__testfixtures__";
import { useOnce } from "./useOnce";
import { useGet } from "./useGet";
import { it, expect, beforeEach, describe, vi } from "vitest";

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

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

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

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

const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
initialProps: { ref: refA1 },
});

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

const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
initialProps: { ref: refA1 },
});

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

const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
initialProps: { ref: refA1 },
});

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

const { result, rerender } = renderHook(({ ref }) => useOnce(ref, getData, isEqual), {
const { result, rerender } = renderHook(({ ref }) => useGet(ref, getData, isEqual), {
initialProps: { ref: refA1 },
});

Expand Down
2 changes: 1 addition & 1 deletion src/internal/useOnce.ts → src/internal/useGet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useStableValue } from "./useStableValue.js";
/**
* @internal
*/
export function useOnce<Value, Error, Reference>(
export function useGet<Value, Error, Reference>(
reference: Reference | undefined,
getData: (ref: Reference) => Promise<Value>,
isEqual: (a: Reference | undefined, b: Reference | undefined) => boolean,
Expand Down

0 comments on commit 0380229

Please sign in to comment.