Skip to content

Commit

Permalink
feat: useAggregateFromServer (#192)
Browse files Browse the repository at this point in the history
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
andipaetzold and renovate[bot] committed Oct 12, 2023
1 parent 03cf52f commit 327a210
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/firestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@
import { ... } from 'react-firehooks/firestore';
```

#### useAggregateFromServer

Returns aggregate of a Firestore Query. Does not update the result once initially calculated.

```javascript
const [data, loading, error] = useAggregateFromServer(query, aggregateSpec);
```

Params:

- `query`: Firestore query the aggregate is calculated for
- `aggregateSpec`: Aggregate specification

Returns:

- `value`: Aggregate of the Firestore query; `undefined` if the aggregate is currently being calculated, or an error occurred
- `loading`: `true` while calculating the aggregate; `false` if the aggregate was calculated successfully or an error occurred
- `error`: `undefined` if no error occurred

## useCountFromServer

Returns the number of documents in the result set of of a Firestore Query. Does not update the count once initially calculated.
Expand Down
1 change: 1 addition & 0 deletions src/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./types.js";
export * from "./useAggregateFromServer.js";
export * from "./useCountFromServer.js";
export * from "./useDocument.js";
export * from "./useDocumentData.js";
Expand Down
13 changes: 13 additions & 0 deletions src/firestore/internal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
aggregateFieldEqual,
AggregateSpec,
DocumentData,
DocumentReference,
DocumentSnapshot,
Expand Down Expand Up @@ -72,3 +74,14 @@ export function isQueryEqual<AppModelType = DocumentData, DbModelType extends Do
const areSameRef = a !== undefined && b !== undefined && queryEqual(a, b);
return areBothUndefined || areSameRef;
}

/**
* @internal
*/
export function isAggregateSpecEqual<T extends AggregateSpec>(a: T, b: T): boolean {
if (Object.keys(a).length === Object.keys(b).length) {
return false;
}

return Object.entries(a).every(([key, value]) => aggregateFieldEqual(value, b[key]));
}
57 changes: 57 additions & 0 deletions src/firestore/useAggregateFromServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { AggregateSpec, AggregateSpecData, FirestoreError, Query } from "firebase/firestore";
import { getAggregateFromServer } from "firebase/firestore";
import type { ValueHookResult } from "../common/types.js";
import { useGet } from "../internal/useGet.js";
import { isAggregateSpecEqual, isQueryEqual } from "./internal.js";

export type UseAggregateFromServerResult<T extends AggregateSpec> = ValueHookResult<AggregateSpecData<T>, FirestoreError>;

interface Reference<T extends AggregateSpec> {
query: Query<unknown>;
aggregateSpec: T;
}

// eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
/**
* @internal
*/
async function getData<T extends AggregateSpec>({ query, aggregateSpec }: Reference<T>): Promise<AggregateSpecData<T>> {
const snap = await getAggregateFromServer(query, aggregateSpec);
return snap.data();
}

// eslint-disable-next-line jsdoc/require-param, jsdoc/require-returns
/**
* @internal
*/
function isEqual<TAggregateSpec extends AggregateSpec, TReference extends Reference<TAggregateSpec>>(
a: TReference | undefined,
b: TReference | undefined,
): boolean {
if (a === undefined && b === undefined) {
return true;
}

const areSameRef =
a !== undefined &&
b !== undefined &&
isQueryEqual(a.query, b.query) &&
isAggregateSpecEqual(a.aggregateSpec, a.aggregateSpec);
return areSameRef;
}

/**
* Returns aggregate of a Firestore Query. Does not update the result once initially calculated.
* @param query Firestore query the aggregate is calculated for
* @param aggregateSpec Aggregate specification
* @returns Size of the result set, loading state, and error
* - value: Aggregate of the Firestore query; `undefined` if the aggregate is currently being calculated, or an error occurred
* - loading: `true` while calculating the aggregate; `false` if the aggregate was calculated successfully or an error occurred
* - error: `undefined` if no error occurred
*/
export function useAggregateFromServer<T extends AggregateSpec>(
query: Query<unknown> | undefined | null,
aggregateSpec: T,
): UseAggregateFromServerResult<T> {
return useGet(query ? { query, aggregateSpec } : undefined, getData, isEqual);
}

0 comments on commit 327a210

Please sign in to comment.