Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: useQueriesOnce & useQueriesDataOnce #239

Merged
merged 6 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading