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

FirebaseError with useDocumentData Hook with [email protected] #315

Open
tomatommy-bs opened this issue Dec 12, 2023 · 4 comments
Open

Comments

@tomatommy-bs
Copy link

Issue: FirebaseError with useDocumentData Hook in React-Firebase Integration

Environment

  • Firebase Version: 10.6.0
  • Next.js Version: 14.0.2
  • React Version: 18+
  • React DOM Version: 18+
  • React Firebase Hooks Version: 5.1.1

Description

I'm encountering an error when trying to fetch a document from Firestore using the useDocumentData hook from react-firebase-hooks/firestore. The goal is to retrieve data from a specific document in Firestore.

Code Snippet

Here's the code snippet where the error occurs:

import { doc, getFirestore } from "firebase/firestore";
import { useDocumentData } from "react-firebase-hooks/firestore";
import { app } from "~/.firebase/client";

const [data, loading, error] = useDocumentData(
	doc(getFirestore(app), "sample-path", "sample-id"),
);

Error Message

However, I receive the following error:

FirebaseError: Expected type 'Query', but it was: a custom DocumentReference object

Additional Context

I followed the documentation for react-firebase-hooks and made sure that the Firestore instance is initialized correctly. The path and ID provided are also correct and exist in the Firestore database.

I'm seeking assistance to understand the cause of this error and how to resolve it. Any insights or suggestions to fix this issue would be greatly appreciated.

@tomatommy-bs
Copy link
Author

FYI,

when I extracted and redefined the relevant code directly into my environment, instead of using it from the library, I was able to run it without any errors.

import {
	CollectionReference,
	DocumentData,
	DocumentReference,
	DocumentSnapshot,
	FirestoreError,
	SnapshotOptions,
	onSnapshot,
	refEqual,
} from "firebase/firestore";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { DocumentDataHook } from "react-firebase-hooks/firestore";
import {
	DataOptions,
	DocumentHook,
	InitialValueOptions,
	Options,
} from "react-firebase-hooks/firestore/dist/firestore/types";
import { RefHook } from "react-firebase-hooks/firestore/dist/util";

type LoadingValue<T, E> = {
	error?: E;
	loading: boolean;
	reset: () => void;
	setError: (error: E) => void;
	setValue: (value?: T) => void;
	value?: T;
};

const useLoadingValue = <T, E>(
	getDefaultValue?: () => T,
): LoadingValue<T, E> => {
	const [value, setValue] = useState<T | undefined>(
		getDefaultValue ? getDefaultValue() : undefined,
	);
	const [error, setError] = useState<E | undefined>(undefined);
	const [loading, setLoading] = useState<boolean>(
		value === undefined || value === null,
	);

	const reset = useCallback(() => {
		const defaultValue = getDefaultValue ? getDefaultValue() : undefined;
		setValue(defaultValue);
		setError(undefined);
		setLoading(defaultValue === undefined || defaultValue === null);
	}, [getDefaultValue]);

	const handleSetValue = useCallback((newValue?: T) => {
		setValue(newValue);
		setError(undefined);
		setLoading(false);
	}, []);

	const handleError = useCallback((newError: E) => {
		setError(newError);
		setLoading(false);
	}, []);

	return {
		value,
		error,
		loading,
		reset,
		setError: handleError,
		setValue: handleSetValue,
	};
};

export const useDocument = <T = DocumentData>(
	docRef?: DocumentReference<T> | null,
	options?: Options,
): DocumentHook<T> => {
	const { error, loading, reset, setError, setValue, value } = useLoadingValue<
		DocumentSnapshot<T>,
		FirestoreError
	>();
	const ref = useIsFirestoreRefEqual<DocumentReference<T>>(docRef, reset);

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		if (!ref.current) {
			setValue(undefined);
			return;
		}
		const unsubscribe = options?.snapshotListenOptions
			? onSnapshot(
					ref.current,
					options.snapshotListenOptions,
					setValue,
					setError,
			  )
			: onSnapshot(ref.current, setValue, setError);

		return () => {
			unsubscribe();
		};
	}, [ref.current, setError, setValue]);

	return [value as DocumentSnapshot<T>, loading, error];
};

export const useDocumentData = <T = DocumentData>(
	docRef?: DocumentReference<T> | null,
	options?: DataOptions<T> & InitialValueOptions<T>,
): DocumentDataHook<T> => {
	const [snapshot, loading, error] = useDocument<T>(docRef, options);

	const value = getValueFromSnapshot(
		snapshot,
		options?.snapshotOptions,
		options?.initialValue,
	);

	return [value, loading, error, snapshot];
};

const getValueFromSnapshot = <T>(
	snapshot: DocumentSnapshot<T> | undefined,
	options?: SnapshotOptions,
	initialValue?: T,
): T | undefined => {
	return useMemo(
		() => (snapshot?.data(options) ?? initialValue) as T | undefined,
		[snapshot, options, initialValue],
	);
};

const useIsFirestoreRefEqual = <
	// biome-ignore lint/suspicious/noExplicitAny: <explanation>
	T extends DocumentReference<any> | CollectionReference<any>,
>(
	value: T | null | undefined,
	onChange?: () => void,
): RefHook<T | null | undefined> => {
	return useComparatorRef(value, isRefEqual, onChange);
};

const isRefEqual = <
	// biome-ignore lint/suspicious/noExplicitAny: <explanation>
	T extends DocumentReference<any> | CollectionReference<any>,
>(
	v1: T | null | undefined,
	v2: T | null | undefined,
): boolean => {
	const bothNull: boolean = !v1 && !v2;
	const equal: boolean = !!v1 && !!v2 && refEqual(v1, v2);
	return bothNull || equal;
};

const useComparatorRef = <T>(
	value: T | null | undefined,
	isEqual: (v1: T | null | undefined, v2: T | null | undefined) => boolean,
	onChange?: () => void,
): RefHook<T | null | undefined> => {
	const ref = useRef(value);
	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		if (!isEqual(value, ref.current)) {
			ref.current = value;
			if (onChange) {
				onChange();
			}
		}
	});
	return ref;
};

@Raamzeez
Copy link

Raamzeez commented Feb 9, 2024

Any fix for this yet? I am encountering the same issue with my project, although I am using useDocument() instead of useDocumentData(). I also extracted the code like @tomatommy-bs and used the useDocument() hook from that instead of the library, but it still gives me the same exact error

@zallesov
Copy link

Happens to me too I receive
FirebaseError: Expected type 'Query', but it was: a custom DocumentReference object
even thou I clearly git it a DocumentRef and not a query

const ref = doc(db, 'projects', '20bcd701-35c4-49e7-b5c3-e0edcf642984')
const [snapshot, isLoading, error] = useDocument(docRef);

@tomatommy-bs
Copy link
Author

@Raamzeez @zallesov

Hi,

As I previously mentioned, up until now I had been extracting TypeScript code from the original code to use it. However, I recently discovered that I can use the library's code directly without any errors. The version hasn't changed since the beginning of this thread. 😟

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants