npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@typed-firestore/react

v1.2.0

Published

Elegant, typed abstractions for Firestore in React applications

Readme

Typed Firestore - React

Elegant, typed abstractions for handling Firestore documents in React applications.

This library is based on the same concepts as @typed-firestore/server, allowing your project to be consistent in both server and client code.

For React Native applications, see @typed-firestore/react-native.

💡 Check out my in-depth article about this library.

Features

  • Avoid having to import and apply types everywhere, by typing your collections once.
  • Use the same convenient document abstraction abstraction as found in typed-firestore-server.
  • Simplify handling and loading states by throwing errors instead of returning them. See throwing errors for motivation.

Installation

pnpm add @typed-firestore/react

Usage

Create a file in which you define refs for all of your database collections, and map each to the appropriate type, as shown below.

// db-refs.ts
import { collection, type CollectionReference } from "firebase/firestore";
import { db } from "./firestore";
import { User, WishlistItem, Book } from "./types";

export const refs = {
  /** For top-level collections it's easy */
  users: collection(db, "users") as CollectionReference<User>,
  books: collection(db, "books") as CollectionReference<Book>,
  /** For sub-collections you could use a function that returns the reference. */
  userWishlist: (userId: string) =>
    collection(
      db,
      `users/${userId}/wishlist`
    ) as CollectionReference<WishlistItem>,

  /** This object never needs to change */
} as const;

Below is an example of how to use the hooks in a component:

import { useDocument } from "@typed-firestore/react";
import { UpdateData } from "firebase/firestore";

export function DisplayName({userId}: {userId: string}) {

  /** Returns user as FsMutableDocument<User> */
  const [user, isLoading] = useDocument(refs.users, userId);

  function handleUpdate() {
    /** Here update is typed to User, and FieldValues are allowed */
    user.update({modifiedAt: FieldValue.serverTimestamp()})
  }

  if (isLoading) {
    return <LoadingIndicator/>;
  }

  /**
   * Typescript knows that user.data is available, because isLoading is false.
   */
  return <div onClick={handleUpdate}>{user.data.displayName}</div>;
}

Notice how we did not need to import the User type, or manually type our update data to satisfy the type constraints. Everything flows from the collection refs. Re-using collection refs also avoids having to remember the names and write them correctly.

Also, because errors are being thrown instead of returned, the Typescript compiler is assured that if isLoading becomes false, the user.data property should be available (because otherwise an error would have been thrown).

API

Hooks

| Hook | Description | | --------------------- | -------------------------------------------------------------------------- | | useDocument | Use a document and subscribe to changes | | useDocumentData | Use only the data part of a document and subscribe to changes | | useDocumentMaybe | Use a document that might not exist | | useDocumentOnce | Use a document once and do not subscribe for changes | | useDocumentDataOnce | Use only the data part of a document once and do not subscribe for changes | | useCollection | Query a collection and subscribe for changes | | useCollectionOnce | Query a collection once and do not subscribe for changes |

Functions

Besides hooks, this library also provides a set of functions that can be used outside of a component. For example when you want to fetch data with ReactQuery.

const { data, isError } = useQuery({
  queryKey: [collectionRef.path, documentId],
  queryFn: () => getDocument(collectionRef, documentId),
});

| Function | Description | | ---------------------------------- | -------------------------------------------------------------- | | getDocument | Fetch a document | | getDocumentData | Fetch only the data part of a document | | getDocumentMaybe | Fetch a document that might not exist | | getDocumentInTransaction | Fetch a document as part of a transaction | | getDocumentInTransactionMaybe | Fetch a document that might not exist as part of a transaction | | getSpecificDocument | Fetch a specific document | | getSpecificDocumentData | Fetch only the data part of a specific document | | getSpecificDocumentInTransaction | Fetch a specific document as part of a transaction |

Working with Documents

The default immutable document type is FsDocument<T>. Use this type when you write functions that take data without needing to change it.

export type FsDocument<T> = {
  id: string;
  data: T;
};

The hooks return mutable documents, but thanks to TS structural typing, you can simply pass them along as if they were immutable FsDocument types.

Each document conveniently contains a typed update function, which only allows you to pass in properties that exist on the type. Firestore FieldValue is allowed to be used to set things like Timestamps.

With some more complex nested data, it can happend that the Firestore UpdateData<T> type doesn't accept your data. For those situations updateWithPartial is available as an alternative.

The original document ref is also available, in case you need functionality that is not covered by this library.

export type FsMutableDocument<T> = {
  id: string;
  data: T;
  ref: DocumentReference<T>;
  update: (data: UpdateData<T>) => Promise<void>;
  updatePartial: (data: PartialWithFieldValue<T>) => Promise<void>;
  delete: () => Promise<void>;
};

See the @typed-firestore/server docs for more info.

Client-Side Mutations

In my projects I prefer to have all mutations happen on the server-side via an API call. You might want to consider that, especially if older versions of your app could be around for a while, like with mobile apps. A bug in client-side code could have lasting effects on the consistency of your database, and time-consuming to have to work around.

Facilitating client-side writes in a safe way also requires writing database rules for your documents, which can get very complex, so mutating documents server-side is not only easier to reason about but also more secure by default.

Throwing Errors

The hooks in this library throw errors, instead of returning them, which is not common practice, but this was a deliberate choice.

In my experience, runtime exceptions for Firestore documents and collection queries are very rare. By throwing we can avoid having to handle errors, and optimize for the happy-path.

The most common errors are:

  1. An index is required but has not been created yet.
  2. The document does not exist.
  3. You do not have permission to read the document.

I think all of these are likely to be caught during development and testing and should not occur in production code.

In some cases it is expected that the document might not exist, so for those situations we have the *Maybe variants like useDocumentMaybe(). These functions do not throw, and simply return undefined if the document does not exist.

This approach also has a nice benefit, because now the loading state is directly tied to the data availability. If you wait for the loading state from useDocument() to become true, the Typescript compiler is also guaranteed that the data exists.

In that sense, you do not even need the loading state at all. It would be sufficient to simply wait for the data to become defined, but for code readability I would still recommend using the loading state variable.

Fork

This library is currently based on a fork of react-firebase-hooks, which was stripped and cleaned up.

The hooks in this package are mostly a stronger-typed, more focussed abstraction on top of the Firestore hooks from that library.

I plan to rewrite the code at some point because it can probably be simplified and improved, but for now it allows us to rely on the functionality without having to write tests.

Sharing Types Between Server and Client

When you share your document types between your server and client code, you might run into a problem with the Timestamp type, because the web and server SDKs currently have slightly incompatible types. The web timestamp has a toJSONmethod which doesn't exist on the server.

The way I work around this, is by using a type alias called FsTimestamp in all of my document types. Then, in each of the client-side or server-side applications, I declare this type globally in a global.d.ts file.

For web it looks like this:

import type { Timestamp } from "firebase/firestore";

declare global {
  type FsTimestamp = Timestamp;
}

For my server code it looks like this:

import type { Timestamp } from "firebase-admin/firestore";

declare global {
  type FsTimestamp = Timestamp;
}