useFirestoreQuery

The useFirestoreQuery hook is used to get or subscribe to a Firestore query using the useQuery hook.

import React from "react";
import { useFirestoreQuery } from "@react-query-firebase/firestore";
import {
  query,
  collection,
  limit,
  QuerySnapshot,
  DocumentData,
} from "firebase/firestore";
import { firestore } from "../firebase";

function Products() {
  const limitTo = 10;

  // Define a query using the Firebase SDK
  const products = query(collection(firestore, "products"), limit(limitTo));

  // Provide the query to the hook
  const products = useFirestoreQuery(["products", { limitTo }], products, {
    subscribe: true,
  });

  if (products.isLoading) {
    return <div>Loading...</div>;
  }

  const snapshot: QuerySnapshot<DocumentData> = products.data;

  return snapshot.docs.map((docSnapshot) => {
    const data = docSnapshot.data();

    return <div key={docSnapshot.id}>{data.name}</div>;
  });
}

By default the query will peform a one-time get request, however by providing the subscribe flag, we can subscribe to any changes on our query in realtime.

If you just need the data of the returned query (rather than the QuerySnapshot), use useFirestoreQueryData instead.

TypeScript

By default the query returns a QuerySnapshot whose data type is DocumentData. If you know the data type of your query documents, either provide the collection instance a converter or a type to the query:

type Product = {
  name: string;
};

// Option 1: Override the generic DocumentData type
const products = collection(firestore, "products");
const query = useFirestoreQuery<Product>("products", products);

// Option 2: Provide a converter
const products = collection(firestore, "products").withConverter<Product>(...);
const query = useFirestoreQuery("products", products);

// The query data on success will be successfully typed:
const snapshot: QuerySnapshot<Product> = query.data;

useFirestoreQueryData

The useFirestoreQuery hook is used to get or subscribe to a Firestore query using the useQuery hook, however instead the document data is returned as an array rather than a QuerySnapshot.

import React from "react";
import { useFirestoreQueryData } from "@react-query-firebase/firestore";
import {
  query,
  collection,
  limit,
  QuerySnapshot,
  DocumentData,
} from "firebase/firestore";
import { firestore } from "../firebase";

function Products() {
  const limitTo = 10;
  const products = query(collection(firestore, "products"), limit(limitTo));

  const query = useFirestoreQueryData(["products", { limitTo }], products, {
    subscribe: true,
  });

  if (query.isLoading) {
    return <div>Loading...</div>;
  }

  const data: DocumentData[] = query.data;

  return data.map((product) => {
    return <div key={product.id}>{product.name}</div>;
  });
}

TypeScript

By default the query returns an array of DocumentData. If you know the data type of your query documents, either provide the collection instance a converter or a type to the query:

type Product = {
  name: string;
};

// Option 1: Override the generic DocumentData type
const products = collection(firestore, "products");
const query = useFirestoreQueryData<Product>("products", products);

// Option 2: Provide a converter
const products = collection(firestore, "products").withConverter<Product>(...);
const query = useFirestoreQueryData("products", products);

// The query data on success will be successfully typed:
const data: Product[] = query.data;

Advanced Usage

Both the useFirestoreQuery and useFirestoreQueryData hooks are similar in the arguments they accept, and both provide tight integration with the useQuery hook allowing you to perform some useful operations on the query data.

Cached vs Server documents

When querying a collection without subscribing to changes, you can specify a source parameter to indiciate whether the cached or server documents should be queried:

useFirestoreQuery("products", query, {
  subscribe: false, // or undefined
  source: "cache", // or 'server'
});

If no value is provided, a combination of cache and server will be used (as default).

Include metadata changes

If querying a collection by subscribing, you can also subscribe to metadata changes on documents.

useFirestoreQuery("products", query, {
  subscribe: true,
  includeMetadataChanges: true,
});

Get a single document in a collection

If you need to query a collection to find a single document but don't know the ID, you can provide a query which provides query constraints and limits the data to a single record. However instead of returning an array, you can instead override the return type of the query to return a single document or object:

/* This example also works with the useFirestoreQueryData hook */

// Users can only have a single active subscription
const findActiveSubscription = query(
  collection(firestore, "subscriptions"),
  where("status", "==", "active"),
  limit(1)
);

type Subscription = {
  id: string;
  status: "active" | "disabled";
};

// Override the return type generic:
const subscription = useFirestoreQuery<Subscription, Subscription | null>(
  "subscription",
  findActiveSubscription,
  {
    subscribe: true,
  },
  {
    select(snapshot: QuerySnapshot<Subscription>): Subscription | null {
      if (snapshot.empty) {
        return null;
      }

      return snapshot.docs[0];
    },
  }
);

// On query success, easily check whether there is an active subscription:
const subscription: Subscription | null = query.data;

Named Queries

If you have the ability to data bundles, you can instead provide a named query to the hook. The hook will first download the query and then cache it based on the Firestore instance and bundle name.

import { loadBundle } from 'firebase/firestore';
import { firestore } from '../firebase';

// Somewhere in your app...
const resp = await fetch('/bundles/products');

// Load in the bundle (named "products")
await loadBundle(firestore, bundle.body)

Elsewhere in your application, provide a named query function instead of a query:

import { useFirestoreQuery, namedQuery } from '@react-query-firebase/firestore';
import { firestore } from '../firebase';

function Products() {
  const query = useFirestoreQuery(['products'], firestore, namedQuery(firestore, 'products'));
}

If the bundle does not exist an error will be thrown.