Library / SDK
prescottprue/redux-firestore avatar
prescottprue/redux-firestore

redux-firestore: keeping Firestore documents in a Redux store

Redux bindings for Firestore

574 stars99 forksJavaScriptMIT

At a glance

What is it?
redux-firestore is a low-level Redux binding for Firestore that mirrors query results into a cache reducer so a component can read them with useSelector, and queues writes through a mutate function. It is the layer under react-redux-firebase, not a React library itself.
Who is it for?
Adopt redux-firestore if you want Firestore data in a Redux store with optimistic writes and are prepared to own the cache invalidation rules, since that is exactly the layer it provides. Do not adopt it if you want React components to subscribe to Firestore directly, because that is what react-redux-firebase does on top of it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A low-level library that react-redux-firebase sits on

The README positions this package precisely: Redux bindings for Firestore, providing a low-level API used in other libraries such as react-redux-firebase. That single sentence tells you what kind of dependency this is. It is not a React library and not a Firestore client. It is the layer that moves data between the Firebase SDK and a Redux store.

The reason the split exists is stated in the Complementary Package section. Most likely you will want react bindings, and for that you need react-redux-firebase, installed with npm install --save react-redux-firebase. That package provides withFirestore and firestoreConnect higher order components, which handle automatically calling redux-firestore internally based on a component's lifecycle, that is, mounting and unmounting.

So the architecture is a stack. Firestore on the bottom, redux-firestore in the middle translating between the database and the store, react-redux-firebase on top giving React components a subscription lifecycle. If you only want React components bound to Firestore, you install the top layer and never touch the middle one directly.

The primary language is JavaScript, the licence is MIT, the default branch is main, and the recent releases are v2.0.2 from 2026-10-02, v2.0.1 from 2023-05-08 and v2.0.0 from 2023-05-01.

Installing, and the peer dependency that constrains your project

Installation is one command, and the README notes that this assumes you are using npm as your package manager.

sh
npm install redux-firestore --save

If you are not using npm, the library is accessible on unpkg at the dist/redux-firestore.min.js path, or you can download it, or point your package manager at it.

The important constraint is the peer dependency rather than the install command. The manifest declares firebase at ^10.9.0 or ^12.0.0 as a peer dependency, which means your Firebase SDK version has to be inside that window. The library is not bundled with a copy of the SDK, so the two versions are resolved by your project, and a mismatch here produces the kind of error that looks like a bug in the binding rather than a version problem.

The runtime dependencies are small: immer at 9.0.21, lodash and reduce-reducers. Immer is the interesting one, because producing a new immutable state for a reducer is exactly what it is for, and a store that mirrors live query results is a reducer that has to produce new state on every document change.

The build setup is a Babel plus webpack pipeline with separate CommonJS, ES module and UMD outputs, and the manifest declares main at lib/index.js, module and jsnext:main at es/index.js, unpkg at dist/redux-firestore.min.js and typings at index.d.ts. The files array ships dist, lib, es, src and index.d.ts, so the TypeScript declarations travel with the package.

Loading data: a query, a listener and a cache read

The API quick start is a four-step pattern that is worth understanding in full, because it explains how the data actually flows.

You construct a Firestore query, attach listeners for updates, and get the data from the selector. The example does it in a functional component.

js
const MyController = () => {
  // 1. construct query
  const taskQuery = {
    collection: `workspace/MySpace/tasks`,
    where:[
      ['status', '<', 1],
      ['deleted', '==', false]
    ],
    orderBy: ['createdAt', 'desc'],
    storeAs: 'tasksStarted',
  }
  
  // 2. load & attached listeners for document changes
  useFirestoreConnect([taskQuery]);

  // 3. Get results
  const tasks = useSelector(state => 
    state.firestore.cache['tasksStarted'].docs
  );
  
  // 4. Display when the data returns
  return (<ol>
    {tasks && tasks.map(({id, title}) => (
      <li key={id}>title</li>
    ))}
  </ol>);
};

The query object is plain data: a collection path, a where clause as an array of arrays, an orderBy, and a storeAs key. The where array uses the operator as a string, so status less than 1 and deleted equals false, in one array. The storeAs value is the name under which the result is cached.

Step two is where the binding does its work. useFirestoreConnect attaches listeners for document changes, and step three reads the result out of the store with a plain selector, reaching into state.firestore.cache under the storeAs key and taking its docs array.

That shape is the whole point of the library. The component never holds Firestore state itself. The listener writes into the store, the store is the single source of truth for rendering, and a Redux-connected component such as useSelector reads it like any other slice.

Writing: mutate queues the change, the UI updates first

The write path is where the library's name for its own behaviour shows up. The README says to use redux-firestore's mutate function to queue changes to Firestore and see the optimistic results instantly in the UI.

js
const MyController = (task) => {
  const changeTitle = useCallback(({id, path, title}) => {
    dispatch(
      createMutate({
        doc: id, 
        collection: path, 
        title
      }))
      .catch((error) => { alert(error) });
  })
  
  return (<TaskView onSave={changeTitle} />);
};

The shape of the payload is worth noting. The doc is the document id, the collection is the path, and the remaining fields are the data to merge. There is no separate update verb in this example, because a mutate object with a doc and a collection is the update.

The returned promise is what makes the optimistic pattern safe rather than reckless. The store is updated immediately so the interface responds at once, and the promise rejects if the write to Firestore fails, which is why the example attaches a catch that surfaces the error to the user. Without that catch, a rejected write leaves the interface showing a change that was never saved.

The saving documentation in the repository covers the variations, with separate sections for optimistic writes, creation, updates, batches, transactions, atomic updates and, notably, a section on what is not supported. The last one is the one to read before assuming a transaction can be expressed.

The cache reducer, and what the documentation does not settle

The overview lists the API surface as Getting Started, then Mutate with its variants, then Query, and then the Cache Reducer with a Structure section. That last one is the interesting part, because the cache is where the design decisions live.

The query documentation is organised the same way, with Syntax, Types, Population and Config Options. Population in particular is the part that distinguishes this from a plain listener: a document can reference another document, and the library resolves those references into the cached tree rather than leaving you with a bare id.

The Config Options section is where the behaviour of live listeners is controlled, and it is also where you decide how much work your application is doing. A Firestore query with a listener keeps a subscription open and keeps writing into the store as documents change, and the cost of that scales with the number of connected queries and the size of their results. A component that mounts a query with a wide filter and stays mounted is a permanent listener, which is a different thing from a one-time fetch.

One thing to note about the repository itself. The version in package.json is 0.0.0-development, which is a placeholder that the release tooling replaces, and the tree carries semantic-release, commitlint, husky, a .yarn directory with a yarnrc.yml, renovate.json and a webpack.config.js. The presence of renovate and semantic-release together tells you releases are automated from commit messages, which is worth knowing if you depend on a specific version.

The README is also honest about what is not done. The Roadmap lists automatic support for documents that have a parameter and a subcollection with the same name, which currently requires storeAs, and support for passing a Ref to setListener in place of a queryConfig object or string. Both are real gaps in a binding that is meant to hide the details of a query.

Where this binding stops being the right layer

There are three boundaries, and the first is the one that decides the whole decision.

The first is the abstraction cost. Redux is a single global store, and putting a live database query result into it means the store holds a copy of data that Firestore also holds. Every document change is a store update, every store subscriber is notified, and the reconciliation between the two copies of the truth is your problem, not Firestore's. That trade is worth it when many components read the same data and you want a single consistent view of it. It is not worth it for a component that fetches once and renders a form.

The second is that the peer dependency range is narrow. firebase at ^10.9.0 or ^12.0.0 means you are constrained to those majors, and a project on a newer Firebase SDK cannot install this binding without a resolution fight. The alternative for React applications is to skip Redux entirely and use the Firebase SDK's own React integration, which gives you a live subscription without a second copy of the data in a store.

The third is the roadmap itself. Documents with a parameter and a subcollection of the same name need a manual storeAs today, and setListener does not accept a Ref instead of a query object. These are not exotic cases, and both land in a document model that many Firestore applications use.

The most useful comparison is with react-redux-firebase, the package above this one. If you want components bound to Firestore with automatic mount and unmount handling, that is the layer to install, and this one is an implementation detail. If you want to drive Firestore from Redux dispatch, manage your own cache shape, or use this library from a non-React project, the low-level API is the point of it. The MIT licence and the small dependency set mean either choice is cheap to try and cheap to undo.

Editorial conclusion

Adopt redux-firestore if you want Firestore data in a Redux store with optimistic writes and are prepared to own the cache invalidation rules, since that is exactly the layer it provides. Do not adopt it if you want React components to subscribe to Firestore directly, because that is what react-redux-firebase does on top of it. Before you commit, check that your firebase peer dependency falls in the supported range, and note the last push was on 2026-10-20 with release v2.0.2 from 2026-10-02.

Frequently asked questions

What does redux-firestore do?

It provides Redux bindings for Firestore, mirroring query results into a cache reducer so a component can read them with useSelector, and queueing writes through a mutate function that returns a promise. It is the low-level API that react-redux-firebase is built on.

How do I install redux-firestore and its React counterpart?

The core library installs with npm install redux-firestore --save, and the React bindings install with npm install --save react-redux-firebase. If you are not using npm, the built library is available on unpkg at the dist/redux-firestore.min.js path.

What Firebase versions does redux-firestore support?

The firebase package is a peer dependency declared as ^10.9.0 or ^12.0.0, so your project's Firebase SDK has to fall inside that range. The library does not bundle a copy of the SDK.

How does the optimistic write pattern work in redux-firestore?

The mutate function queues the change to Firestore and the optimistic result appears in the UI immediately. The call returns a promise that rejects if the write fails, so attaching a catch is what keeps the interface from showing a change that was never saved.

What features are still on the redux-firestore roadmap?

Automatic support for documents that have a parameter and a subcollection with the same name, which currently requires storeAs, and support for passing a Ref to setListener in place of a queryConfig object or string.

Official sources

  1. Issues
  2. License: MIT
  3. prescottprue/redux-firestore on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/prescottprue-redux-firestore.svg)](https://hysenlabs.com/projects/prescottprue-redux-firestore)

Was this useful?