Typescript

The library comes with support for a full typesafe API.

Response types

When fetching a or modifying documents the default return type from Firestore is a QuerySnapshot or DocumentSnapshot whose data type is DocumentData.

For a type safe application, this is dangerous. There are 2 ways to ensure your data is returned type safe, either explicilty or inferred:

Explcit types

Provide the type declaration to the hooks directly:

type Product = {
  name: string;
  price: number;
}

useFirestoreQuery<Product>(...); // QuerySnapshot<Product>
useFirestoreQueryData<Product>(...); // Product[]

useFirestoreDocument<Product>(); // DocumentSnapshot<Product>
useFirestoreDocumentData<Product>(); // Product | null

Inferred types

The hooks will inferr any types from the provided reference, for example you could define Firestore converters:

type Product = {
  name: string;
  price: number;
}

const ref = collection(firebase, 'products').withConverter<Product>(...);

useFirestoreQuery('...', ref); // QuerySnapshot<Product>
useFirestoreQueryData('...', ref); // Product[]

const docRef = ref.doc('123');

useFirestoreDocument('...', docRef); // DocumentSnapshot<Product>
useFirestoreDocumentData('...', docRef); // Product | null

Data modifications

When returning modified data, you can pass a second type to the hooks for typesafe result data.

type Product = {
  name: string;
  price: number;
};

const query = useFirestoreQuery<Product, number | null>("...", ref, undefined, {
  select(snapshot: QuerySnapshot<Product>): number | null {
    if (!snapshot.exists()) {
      return null;
    }

    return snapshot.data().price;
  },
});

if (query.isSuccess) {
  const price = query.data; // number | null
}

Mutations

When working with the various mutation hooks, you can provide types to override the expected mutation data.

By default, the mutation value will be inferred from the provided reference, for example:

type Product = {
  name: string;
  price: number;
}

const ref = collection(firebase, 'products').withConverter<Product>(...);

const mutation = useFirestoreCollectionMutation(ref);

// mutate expects a Product
mutation.mutate({
  name: 'New product',
  price: 10,
});

You can also optionally provide a type override:

const mutation = useFirestoreCollectionMutation<Product>(ref);

// mutate expects a Product
mutation.mutate({
  name: "New product",
  price: 10,
});

If working with transactions, you can provide a custom type as the expected response of the transaction:

const ref = collection(firebase, "posts").doc("123");

const mutation = useFirestoreTransaction<number>(firestore, async (tsx) => {
  const post = await tsx.get(ref);
  const newLikes = post.data().likes + 1;

  tsx.update(ref, { likes: newLikes });

  // Returning a number is required
  return newLikes;
});

if (mutation.isSuccess) {
  console.log("New likes: ", mutation.data);
}