Skip to content

Commit

Permalink
feat: refetch/resubscribe when options change (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
andipaetzold committed Sep 15, 2023
1 parent 4af632f commit f9c30ea
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 96 deletions.
4 changes: 3 additions & 1 deletion src/database/internal.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable jsdoc/require-returns */
/* eslint-disable jsdoc/require-param */
import { Query } from "firebase/database";
import type { DataSnapshot, Query } from "firebase/database";

/**
* @internal
Expand All @@ -10,3 +10,5 @@ export function isQueryEqual(a: Query | undefined, b: Query | undefined): boolea
const areSameRef = a !== undefined && b !== undefined && a.isEqual(b);
return areBothUndefined || areSameRef;
}

export const defaultConverter = (snap: DataSnapshot) => snap.val();
6 changes: 3 additions & 3 deletions src/database/useObjectValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback } from "react";
import type { ValueHookResult } from "../common/index.js";
import { useListen, UseListenOnChange } from "../internal/useListen.js";
import { LoadingState } from "../internal/useLoadingValue.js";
import { isQueryEqual } from "./internal.js";
import { defaultConverter, isQueryEqual } from "./internal.js";

export type UseObjectValueResult<Value = unknown> = ValueHookResult<Value, Error>;

Expand All @@ -30,7 +30,7 @@ export function useObjectValue<Value = unknown>(
query: Query | undefined | null,
options?: UseObjectValueOptions<Value>,
): UseObjectValueResult<Value> {
const { converter = (snap: DataSnapshot) => snap.val() } = options ?? {};
const { converter = defaultConverter, initialValue = LoadingState } = options ?? {};

const onChange: UseListenOnChange<Value, Error, Query> = useCallback(
(stableQuery, next, error) => onValue(stableQuery, (snap) => next(converter(snap)), error),
Expand All @@ -39,5 +39,5 @@ export function useObjectValue<Value = unknown>(
[],
);

return useListen(query ?? undefined, onChange, isQueryEqual, options?.initialValue ?? LoadingState);
return useListen(query ?? undefined, onChange, isQueryEqual, initialValue);
}
4 changes: 2 additions & 2 deletions src/database/useObjectValueOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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 { isQueryEqual } from "./internal.js";
import { defaultConverter, isQueryEqual } from "./internal.js";

export type UseObjectValueOnceResult<Value = unknown> = ValueHookResult<Value, Error>;

Expand All @@ -27,7 +27,7 @@ export function useObjectValueOnce<Value = unknown>(
query: Query | undefined | null,
options?: UseObjectValueOnceOptions<Value>,
): UseObjectValueOnceResult<Value> {
const { converter = (snap: DataSnapshot) => snap.val() } = options ?? {};
const { converter = defaultConverter } = options ?? {};

const getData = useCallback(async (stableQuery: Query) => {
const snap = await get(stableQuery);
Expand Down
19 changes: 11 additions & 8 deletions src/firestore/useDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,20 @@ export function useDocument<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
options?: UseDocumentOptions,
): UseDocumentResult<Value> {
const { snapshotListenOptions = {} } = options ?? {};
const { snapshotListenOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};

const onChange: UseListenOnChange<DocumentSnapshot<Value>, FirestoreError, DocumentReference<Value>> = useCallback(
(stableRef, next, error) =>
onSnapshot<Value>(stableRef, snapshotListenOptions, {
next,
error,
}),
// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
onSnapshot<Value>(
stableRef,
{ includeMetadataChanges },
{
next,
error,
},
),
[includeMetadataChanges],
);

return useListen(reference ?? undefined, onChange, isDocRefEqual, LoadingState);
Expand Down
20 changes: 12 additions & 8 deletions src/firestore/useDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,21 @@ export function useDocumentData<Value extends DocumentData = DocumentData>(
reference: DocumentReference<Value> | undefined | null,
options?: UseDocumentDataOptions<Value>,
): UseDocumentDataResult<Value> {
const { snapshotListenOptions = {}, snapshotOptions } = options ?? {};
const { snapshotListenOptions, snapshotOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const onChange: UseListenOnChange<Value, FirestoreError, DocumentReference<Value>> = useCallback(
(stableRef, next, error) =>
onSnapshot<Value>(stableRef, snapshotListenOptions, {
next: (snap) => next(snap.data(snapshotOptions)),
error,
}),
// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
onSnapshot<Value>(
stableRef,
{ includeMetadataChanges },
{
next: (snap) => next(snap.data({ serverTimestamps })),
error,
},
),
[includeMetadataChanges, serverTimestamps],
);

return useListen(reference ?? undefined, onChange, isDocRefEqual, options?.initialValue ?? LoadingState);
Expand Down
15 changes: 9 additions & 6 deletions src/firestore/useDocumentDataOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ export function useDocumentDataOnce<Value extends DocumentData = DocumentData>(
options?: UseDocumentDataOnceOptions,
): UseDocumentDataOnceResult<Value> {
const { source = "default", snapshotOptions } = options ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const getData = useCallback(
async (stableRef: DocumentReference<Value>) => {
const snap = await getDocFromSource(stableRef, source);
return snap.data({ serverTimestamps });
},
[serverTimestamps, source],
);

const getData = useCallback(async (stableRef: DocumentReference<Value>) => {
const snap = await getDocFromSource(stableRef, source);
return snap.data(snapshotOptions);
// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return useOnce(reference ?? undefined, getData, isDocRefEqual);
}
8 changes: 2 additions & 6 deletions src/firestore/useDocumentOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ export function useDocumentOnce<Value extends DocumentData = DocumentData>(
): UseDocumentOnceResult<Value> {
const { source = "default" } = options ?? {};

const getData = useCallback(
(stableRef: DocumentReference<Value>) => getDocFromSource(stableRef, source),
// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const getData = useCallback((stableRef: DocumentReference<Value>) => getDocFromSource(stableRef, source), [source]);

return useOnce(reference ?? undefined, getData, isDocRefEqual);
}
21 changes: 12 additions & 9 deletions src/firestore/useQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ export function useQueries<Values extends ReadonlyArray<DocumentData> = Readonly
queries: { [Index in keyof Values]: Query<Values[Index]> },
options?: UseQueriesOptions,
): UseQueriesResult<Values> {
const { snapshotListenOptions = {} } = options ?? {};
const { snapshotListenOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};

const onChange: UseMultiListenChange<QuerySnapshot<Values[number]>, FirestoreError, Query<Values[number]>> = useCallback(
(query, next, error) =>
onSnapshot(query, snapshotListenOptions, {
next,
error,
}),

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
onSnapshot(
query,
{ includeMetadataChanges },
{
next,
error,
},
),
[includeMetadataChanges],
);

// @ts-expect-error `useMultiListen` assumes a single value type
Expand Down
21 changes: 13 additions & 8 deletions src/firestore/useQueriesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,22 @@ export function useQueriesData<Values extends ReadonlyArray<DocumentData> = Read
queries: { [Index in keyof Values]: Query<Values[Index]> },
options?: UseQueriesDataOptions,
): UseQueriesDataResult<Values> {
const { snapshotListenOptions = {}, snapshotOptions = {} } = options ?? {};
const { snapshotListenOptions, snapshotOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const onChange: UseMultiListenChange<Values[number], FirestoreError, Query<Values[number]>> = useCallback(
(query, next, error) =>
onSnapshot(query, snapshotListenOptions, {
next: (snap) => next(snap.docs.map((doc) => doc.data(snapshotOptions))),
error,
}),
onSnapshot(
query,
{ includeMetadataChanges },
{
next: (snap) => next(snap.docs.map((doc) => doc.data({ serverTimestamps }))),
error,
},
),

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
[includeMetadataChanges, serverTimestamps],
);

// @ts-expect-error `useMultiListen` assumes a single value type
Expand Down
20 changes: 11 additions & 9 deletions src/firestore/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@ export function useQuery<Value extends DocumentData = DocumentData>(
query: Query<Value> | undefined | null,
options?: UseQueryOptions,
): UseQueryResult<Value> {
const { snapshotListenOptions = {} } = options ?? {};
const { snapshotListenOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};

const onChange: UseListenOnChange<QuerySnapshot<Value>, FirestoreError, Query<Value>> = useCallback(
(stableQuery, next, error) =>
onSnapshot<Value>(stableQuery, snapshotListenOptions, {
next,
error,
}),

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
onSnapshot<Value>(
stableQuery,
{ includeMetadataChanges },
{
next,
error,
},
),
[includeMetadataChanges],
);

return useListen(query ?? undefined, onChange, isQueryEqual, LoadingState);
Expand Down
28 changes: 19 additions & 9 deletions src/firestore/useQueryData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,28 @@ export function useQueryData<Value extends DocumentData = DocumentData>(
query: Query<Value> | undefined | null,
options?: UseQueryDataOptions<Value>,
): UseQueryDataResult<Value> {
const { snapshotListenOptions = {}, snapshotOptions = {} } = options ?? {};
const { snapshotListenOptions, snapshotOptions } = options ?? {};
const { includeMetadataChanges } = snapshotListenOptions ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

const onChange: UseListenOnChange<Value[], FirestoreError, Query<Value>> = useCallback(
(stableQuery, next, error) =>
onSnapshot<Value>(stableQuery, snapshotListenOptions, {
next: (snap) => next(snap.docs.map((doc) => doc.data(snapshotOptions))),
error,
}),

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
onSnapshot<Value>(
stableQuery,
{ includeMetadataChanges },
{
next: (snap) =>
next(
snap.docs.map((doc) =>
doc.data({
serverTimestamps,
}),
),
),
error,
},
),
[includeMetadataChanges, serverTimestamps],
);

return useListen(query ?? undefined, onChange, isQueryEqual, options?.initialValue ?? LoadingState);
Expand Down
16 changes: 9 additions & 7 deletions src/firestore/useQueryDataOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ export function useQueryDataOnce<Value extends DocumentData = DocumentData>(
query: Query<Value> | undefined | null,
options?: UseQueryDataOnceOptions,
): UseQueryDataOnceResult<Value> {
const { source = "default", snapshotOptions = {} } = options ?? {};
const { source = "default", snapshotOptions } = options ?? {};
const { serverTimestamps } = snapshotOptions ?? {};

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

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return useOnce(query ?? undefined, getData, isQueryEqual);
}
7 changes: 1 addition & 6 deletions src/firestore/useQueryOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ export function useQueryOnce<Value extends DocumentData = DocumentData>(
): UseQueryOnceResult<Value> {
const { source = "default" } = options ?? {};

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

// TODO: add options as dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
return useOnce(query ?? undefined, getData, isQueryEqual);
}
5 changes: 1 addition & 4 deletions src/internal/useListen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ export function useListen<Value, Error, Reference>(
const unsubscribe = onChange(stableRef, setValue, setError);
return () => unsubscribe();
}

// TODO: double check dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stableRef]);
}, [stableRef, onChange, setError, setLoading, setValue]);

return useMemo(() => [value, loading, error], [value, loading, error]);
}
5 changes: 1 addition & 4 deletions src/internal/useMultiListen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ export function useMultiListen<Value, Error, Reference>(
(error) => setError(refIndex, error),
);
}

// TODO: double check dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [references]);
}, [references, isEqualRef, onChange, setError, setLoading, setValue]);

// unsubscribe and cleanup on unmount
useEffect(
Expand Down
5 changes: 1 addition & 4 deletions src/internal/useOnce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,7 @@ export function useOnce<Value, Error, Reference>(
}
}
})();

// TODO: double check dependencies
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stableRef]);
}, [stableRef, getData, isEqual, setValue, setLoading, isMounted, setError]);

return useMemo(() => [value, loading, error], [value, loading, error]);
}
4 changes: 2 additions & 2 deletions src/internal/useStableValue.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect, useState as useRef } from "react";
import { useEffect, useState } from "react";

/**
* @internal
*/
export function useStableValue<Value>(value: Value, isEqual: (a: Value, b: Value) => boolean): Value {
const [state, setState] = useRef(value);
const [state, setState] = useState(value);

useEffect(() => {
if (!isEqual(state, value)) {
Expand Down

0 comments on commit f9c30ea

Please sign in to comment.