Skip to content

Commit

Permalink
feat: require firebase 10.5.0 or later (#247)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: require firebase 10.5.0 or later
  • Loading branch information
andipaetzold committed Oct 12, 2023
1 parent 52949b2 commit 03cf52f
Show file tree
Hide file tree
Showing 17 changed files with 790 additions and 1,041 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ yarn add react-firehooks

## Compatibility

- [firebase](https://www.npmjs.com/package/firebase): 9.11.0 or later
- [firebase](https://www.npmjs.com/package/firebase): 10.5.0 or later
- [react](https://www.npmjs.com/package/react): 16.8.0 or later

## Usage
Expand Down
5 changes: 5 additions & 0 deletions migrations/v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Migrate from v3 to v4

## Peer dependency

This library now requires firebase 10.5.0 or later
1,556 changes: 628 additions & 928 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-react": "7.33.2",
"eslint-plugin-react-hooks": "4.6.0",
"firebase": "9.11.0",
"firebase": "10.5.0",
"happy-dom": "12.2.2",
"husky": "8.0.3",
"lint-staged": "14.0.1",
Expand All @@ -91,7 +91,7 @@
"lint-staged": "lint-staged"
},
"peerDependencies": {
"firebase": "^9.11.0 || ^10.0.0",
"firebase": "^10.5.0",
"react": ">=16.8.0"
},
"lint-staged": {
Expand Down
23 changes: 13 additions & 10 deletions src/firestore/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import type { Source } from "./types.js";
/**
* @internal
*/
export async function getDocFromSource<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value>,
export async function getDocFromSource<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
reference: DocumentReference<AppModelType, DbModelType>,
source: Source,
): Promise<DocumentSnapshot<Value>> {
): Promise<DocumentSnapshot<AppModelType, DbModelType>> {
switch (source) {
case "cache":
return await getDocFromCache(reference);
Expand All @@ -35,10 +35,10 @@ export async function getDocFromSource<Value extends DocumentData = DocumentData
/**
* @internal
*/
export async function getDocsFromSource<Value extends DocumentData = DocumentData>(
query: Query<Value>,
export async function getDocsFromSource<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
query: Query<AppModelType, DbModelType>,
source: Source,
): Promise<QuerySnapshot<Value>> {
): Promise<QuerySnapshot<AppModelType, DbModelType>> {
switch (source) {
case "cache":
return await getDocsFromCache(query);
Expand All @@ -52,9 +52,9 @@ export async function getDocsFromSource<Value extends DocumentData = DocumentDat
/**
* @internal
*/
export function isDocRefEqual<Value>(
a: DocumentReference<Value> | undefined,
b: DocumentReference<Value> | undefined,
export function isDocRefEqual<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
a: DocumentReference<AppModelType, DbModelType> | undefined,
b: DocumentReference<AppModelType, DbModelType> | undefined,
): boolean {
const areBothUndefined = a === undefined && b === undefined;
const areSameRef = a !== undefined && b !== undefined && refEqual(a, b);
Expand All @@ -64,7 +64,10 @@ export function isDocRefEqual<Value>(
/**
* @internal
*/
export function isQueryEqual<Value>(a: Query<Value> | undefined, b: Query<Value> | undefined): boolean {
export function isQueryEqual<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
a: Query<AppModelType, DbModelType> | undefined,
b: Query<AppModelType, DbModelType> | undefined,
): boolean {
const areBothUndefined = a === undefined && b === undefined;
const areSameRef = a !== undefined && b !== undefined && queryEqual(a, b);
return areBothUndefined || areSameRef;
Expand Down
22 changes: 12 additions & 10 deletions src/firestore/useDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import { useListen, UseListenOnChange } from "../internal/useListen.js";
import { LoadingState } from "../internal/useLoadingValue.js";
import { isDocRefEqual } from "./internal.js";

export type UseDocumentResult<Value extends DocumentData = DocumentData> = ValueHookResult<
DocumentSnapshot<Value>,
FirestoreError
>;
export type UseDocumentResult<AppModelType = DocumentData> = ValueHookResult<DocumentSnapshot<AppModelType>, FirestoreError>;

/**
* Options to configure the subscription
Expand All @@ -26,24 +23,29 @@ export interface UseDocumentOptions {

/**
* Returns and updates a DocumentSnapshot of a Firestore DocumentReference
* @template Value Type of the document data
* @template AppModelType Shape of the data after it was converted from firestore
* @template DbModelType Shape of the data in firestore
* @param reference Firestore DocumentReference that will be subscribed to
* @param options Options to configure the subscription
* @returns Document snapshot, loading state, and error
* - value: DocumentSnapshot; `undefined` if document does not exist, is currently being fetched, or an error occurred
* - loading: `true` while fetching the document; `false` if the document was fetched successfully or an error occurred
* - error: `undefined` if no error occurred
*/
export function useDocument<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
export function useDocument<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
reference: DocumentReference<AppModelType, DbModelType> | undefined | null,
options?: UseDocumentOptions,
): UseDocumentResult<Value> {
): UseDocumentResult<AppModelType> {
const { snapshotListenOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};

const onChange: UseListenOnChange<DocumentSnapshot<Value>, FirestoreError, DocumentReference<Value>> = useCallback(
const onChange: UseListenOnChange<
DocumentSnapshot<AppModelType, DbModelType>,
FirestoreError,
DocumentReference<AppModelType, DbModelType>
> = useCallback(
(stableRef, next, error) =>
onSnapshot<Value>(
onSnapshot(
stableRef,
{ includeMetadataChanges },
{
Expand Down
25 changes: 15 additions & 10 deletions src/firestore/useDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ import { useListen, UseListenOnChange } from "../internal/useListen.js";
import { LoadingState } from "../internal/useLoadingValue.js";
import { isDocRefEqual } from "./internal.js";

export type UseDocumentDataResult<Value extends DocumentData = DocumentData> = ValueHookResult<Value, FirestoreError>;
export type UseDocumentDataResult<AppModelType = DocumentData> = ValueHookResult<AppModelType, FirestoreError>;

/**
* Options to configure the subscription
*/
export interface UseDocumentDataOptions<Value extends DocumentData = DocumentData> {
export interface UseDocumentDataOptions<AppModelType = DocumentData> {
snapshotListenOptions?: SnapshotListenOptions;
snapshotOptions?: SnapshotOptions;
initialValue?: Value;
initialValue?: AppModelType;
}

/**
* Returns and updates the data of a Firestore DocumentReference
* @template Value Type of the document data
* @template AppModelType Shape of the data after it was converted from firestore
* @template DbModelType Shape of the data in firestore
* @param reference Firestore DocumentReference that will be subscribed to
* @param options Options to configure the subscription
* `initialValue`: Value that is returned while the document is being fetched.
Expand All @@ -34,17 +35,21 @@ export interface UseDocumentDataOptions<Value extends DocumentData = DocumentDat
* - loading: `true` while fetching the document; `false` if the document was fetched successfully or an error occurred
* - error: `undefined` if no error occurred
*/
export function useDocumentData<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
options?: UseDocumentDataOptions<Value>,
): UseDocumentDataResult<Value> {
export function useDocumentData<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
reference: DocumentReference<AppModelType, DbModelType> | undefined | null,
options?: UseDocumentDataOptions<AppModelType>,
): UseDocumentDataResult<AppModelType> {
const { snapshotListenOptions, snapshotOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const onChange: UseListenOnChange<Value, FirestoreError, DocumentReference<Value>> = useCallback(
const onChange: UseListenOnChange<
AppModelType,
FirestoreError,
DocumentReference<AppModelType, DbModelType>
> = useCallback(
(stableRef, next, error) =>
onSnapshot<Value>(
onSnapshot(
stableRef,
{ includeMetadataChanges },
{
Expand Down
13 changes: 7 additions & 6 deletions src/firestore/useDocumentDataOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useGet } from "../internal/useGet.js";
import { getDocFromSource, isDocRefEqual } from "./internal.js";
import type { Source } from "./types.js";

export type UseDocumentDataOnceResult<Value extends DocumentData = DocumentData> = ValueHookResult<Value, FirestoreError>;
export type UseDocumentDataOnceResult<AppModelType = DocumentData> = ValueHookResult<AppModelType, FirestoreError>;

/**
* Options to configure how the document is fetched
Expand All @@ -17,23 +17,24 @@ export interface UseDocumentDataOnceOptions {

/**
* Returns the data of a Firestore DocumentReference
* @template Value Type of the document data
* @template AppModelType Shape of the data after it was converted from firestore
* @template DbModelType Shape of the data in firestore
* @param reference Firestore DocumentReference that will be subscribed to
* @param options Options to configure how the document is fetched
* @returns Document data, loading state, and error
* - value: Document data; `undefined` if document does not exist, is currently being fetched, or an error occurred
* - loading: `true` while fetching the document; `false` if the document was fetched successfully or an error occurred
* - error: `undefined` if no error occurred
*/
export function useDocumentDataOnce<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
export function useDocumentDataOnce<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
reference: DocumentReference<AppModelType, DbModelType> | undefined | null,
options?: UseDocumentDataOnceOptions,
): UseDocumentDataOnceResult<Value> {
): UseDocumentDataOnceResult<AppModelType> {
const { source = "default", snapshotOptions } = options ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const getData = useCallback(
async (stableRef: DocumentReference<Value>) => {
async (stableRef: DocumentReference<AppModelType, DbModelType>) => {
const snap = await getDocFromSource(stableRef, source);
return snap.data({ serverTimestamps });
},
Expand Down
18 changes: 11 additions & 7 deletions src/firestore/useDocumentOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { useGet } from "../internal/useGet.js";
import { getDocFromSource, isDocRefEqual } from "./internal.js";
import type { Source } from "./types.js";

export type UseDocumentOnceResult<Value extends DocumentData = DocumentData> = ValueHookResult<
DocumentSnapshot<Value>,
export type UseDocumentOnceResult<AppModelType = DocumentData> = ValueHookResult<
DocumentSnapshot<AppModelType>,
FirestoreError
>;

Expand All @@ -19,21 +19,25 @@ export interface UseDocumentOnceOptions {

/**
* Returns the DocumentSnapshot of a Firestore DocumentReference. Does not update the DocumentSnapshot once initially fetched
* @template Value Type of the document data
* @template AppModelType Shape of the data after it was converted from firestore
* @template DbModelType Shape of the data in firestore
* @param reference Firestore DocumentReference that will be fetched
* @param options Options to configure how the document is fetched
* @returns DocumentSnapshot, loading state, and error
* - value: DocumentSnapshot; `undefined` if document does not exist, is currently being fetched, or an error occurred
* - loading: `true` while fetching the document; `false` if the document was fetched successfully or an error occurred
* - error: `undefined` if no error occurred
*/
export function useDocumentOnce<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
export function useDocumentOnce<AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData>(
reference: DocumentReference<AppModelType, DbModelType> | undefined | null,
options?: UseDocumentOnceOptions,
): UseDocumentOnceResult<Value> {
): UseDocumentOnceResult<AppModelType> {
const { source = "default" } = options ?? {};

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

return useGet(reference ?? undefined, getData, isDocRefEqual);
}
24 changes: 16 additions & 8 deletions src/firestore/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { ValueHookResult } from "../common/types.js";
import { useMultiListen, UseMultiListenChange } from "../internal/useMultiListen.js";
import { isQueryEqual } from "./internal.js";

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

/**
* Options to configure the subscription
Expand All @@ -17,22 +17,30 @@ export interface UseQueriesOptions {

/**
* Returns and updates a QuerySnapshot of multiple Firestore queries
* @template Values Tuple of types of the collection data
* @template AppModelTypes Tuple of shapes of the data after it was converted from firestore
* @template DbModelTypes Tuple of shapes of the data in firestore
* @param queries Firestore queries that will be subscribed to
* @param options Options to configure the subscription
* @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 useQueries<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
queries: { [Index in keyof Values]: Query<Values[Index]> },
export function useQuerie<
AppModelTypes extends ReadonlyArray<unknown> = ReadonlyArray<DocumentData>,
DbModelTypes extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>,
>(
queries: { [Index in keyof AppModelTypes]: Query<AppModelTypes[Index], DbModelTypes[number]> },
options?: UseQueriesOptions,
): UseQueriesResult<Values> {
): UseQueriesResult<AppModelTypes> {
const { snapshotListenOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};

const onChange: UseMultiListenChange<QuerySnapshot<Values[number]>, FirestoreError, Query<Values[number]>> = useCallback(
const onChange: UseMultiListenChange<
QuerySnapshot<AppModelTypes[number], DbModelTypes[number]>,
FirestoreError,
Query<AppModelTypes[number], DbModelTypes[number]>
> = useCallback(
(query, next, error) =>
onSnapshot(
query,
Expand Down
24 changes: 16 additions & 8 deletions src/firestore/useQueriesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { ValueHookResult } from "../common/types.js";
import { useMultiListen, UseMultiListenChange } from "../internal/useMultiListen.js";
import { isQueryEqual } from "./internal.js";

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

/**
* Options to configure the subscription
Expand All @@ -18,23 +18,31 @@ export interface UseQueriesDataOptions {

/**
* Returns and updates a the document data of multiple Firestore queries
* @template Values Tuple of types of the collection data
* @template AppModelTypes Tuple of shapes of the data after it was converted from firestore
* @template DbModelTypes Tuple of shapes of the data in firestore
* @param queries Firestore queries that will be subscribed to
* @param options Options to configure the subscription
* @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 useQueriesData<Values extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>>(
queries: { [Index in keyof Values]: Query<Values[Index]> },
export function useQueriesData<
AppModelTypes extends ReadonlyArray<unknown> = ReadonlyArray<DocumentData>,
DbModelTypes extends ReadonlyArray<DocumentData> = ReadonlyArray<DocumentData>,
>(
queries: { [Index in keyof AppModelTypes]: Query<AppModelTypes[Index], DbModelTypes[number]> },
options?: UseQueriesDataOptions,
): UseQueriesDataResult<Values> {
): UseQueriesDataResult<AppModelTypes> {
const { snapshotListenOptions, snapshotOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const onChange: UseMultiListenChange<Values[number], FirestoreError, Query<Values[number]>> = useCallback(
const onChange: UseMultiListenChange<
AppModelTypes[number],
FirestoreError,
Query<AppModelTypes[number], DbModelTypes[number]>
> = useCallback(
(query, next, error) =>
onSnapshot(
query,
Expand Down

0 comments on commit 03cf52f

Please sign in to comment.