Git Product home page Git Product logo

redux-firestore's Introduction

redux-firestore

NPM version NPM downloads License Code Style Dependency Status Build Status Code Coverage

Gitter

Redux bindings for Firestore. Provides low-level API used in other libraries such as react-redux-firebase

Installation

npm install redux-firestore --save

This assumes you are using npm as your package manager.

If you're not, you can access the library on unpkg, download it, or point your package manager to it. Theres more on this in the Builds section below

Complementary Package

Most likely, you'll want react bindings, for that you will need react-redux-firebase. You can install the current version it by running:

npm install --save react-redux-firebase

react-redux-firebase provides withFirestore and firestoreConnect higher order components, which handle automatically calling redux-firestore internally based on component's lifecycle (i.e. mounting/un-mounting)

Use

import { createStore, combineReducers, compose } from 'redux'
import { reduxFirestore, firestoreReducer } from 'redux-firestore'
import firebase from 'firebase'
import 'firebase/firestore'

const firebaseConfig = {} // from Firebase Console

// Initialize firebase instance
firebase.initializeApp(firebaseConfig)
// Initialize Cloud Firestore through Firebase
firebase.firestore();

// Add reduxFirestore store enhancer to store creator
const createStoreWithFirebase = compose(
  reduxFirestore(firebase), // firebase instance as first argument
)(createStore)

// Add Firebase to reducers
const rootReducer = combineReducers({
  firestore: firestoreReducer
})

// Create store with reducers and initial state
const initialState = {}
const store = createStoreWithFirebase(rootReducer, initialState)

Then pass store to your component's context using react-redux's Provider:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';

render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

Call Firestore

Firestore Instance

Functional Components

It is common to make react components "functional" meaning that the component is just a function instead of being a class which extends React.Component. This can be useful, but can limit usage of lifecycle hooks and other features of Component Classes. recompose helps solve this by providing Higher Order Component functions such as withContext, lifecycle, and withHandlers.

import { connect } from 'react-redux'
import {
  compose,
  withHandlers,
  lifecycle,
  withContext,
  getContext
} from 'recompose'

const withStore = compose(
  withContext({ store: PropTypes.object }, () => {}),
  getContext({ store: PropTypes.object }),
)

const enhance = compose(
  withStore,
  withHandlers({
    loadData: props => () => props.store.firestore.get('todos'),
    onDoneClick: props => (key, done = false) =>
      props.store.firestore.update(`todos/${key}`, { done }),
    onNewSubmit: props => newTodo =>
      props.store.firestore.add('todos', { ...newTodo, owner: 'Anonymous' }),
  }),
  lifecycle({
    componentWillMount(props) {
      props.loadData()
    }
  }),
  connect(({ firebase }) => ({ // state.firebase
    todos: firebase.ordered.todos,
  }))
)

export default enhance(SomeComponent)

For more information on using recompose visit the docs

Component Class
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { isEqual } from 'lodash'
import { watchEvents, unWatchEvents } from './actions/query'
import { getEventsFromInput, createCallable } from './utils'

class Todos extends Component {
  static contextTypes = {
    store: PropTypes.object.isRequired
  }

  componentWillMount () {
    const { firestore } = this.context.store
    firestore.get('todos')
  }

  render () {
    return (
      <div>
        {
          todos.map(todo => (
            <div key={todo.id}>
              {JSON.stringify(todo)}
            </div>
          ))
        }
      </div>
    )
  }
}

export default connect((state) => ({
  todos: state.firestore.ordered.todos
}))(Todos)

Types of Queries

get
props.store.firestore.get({ collection: 'cities' }),
// store.firestore.get({ collection: 'cities', doc: 'SF' }), // doc
onSnapshot/setListener
store.firestore.onSnapshot({ collection: 'cities' }),
// store.firestore.setListener({ collection: 'cities' }), // alias
// store.firestore.setListener({ collection: 'cities', doc: 'SF' }), // doc

setListeners

store.firestore.setListeners([
  { collection: 'cities' },
  { collection: 'users' },
]),

Query Options

Collection
{ collection: 'cities' },
// or string equivalent
// store.firestore.get('cities'),
Document
{ collection: 'cities', doc: 'SF' },
// or string equivalent
// props.store.firestore.get('cities/SF'),
Sub Collections
{ collection: 'cities', doc: 'SF', subcollections: [{ collection: 'zipcodes' }] },
// or string equivalent
// props.store.firestore.get('cities/SF'),
Where

To create a single where call, pass a single argument array to the where parameter:

{
  collection: 'cities',
  where: ['state', '==', 'CA']
},

Multiple where queries are as simple as passing multiple argument arrays (each one representing a where call):

{
  collection: 'cities',
  where: [
    ['state', '==', 'CA'],
    ['population', '<', 100000]
  ]
},

Should only be used with collections

orderBy

To create a single orderBy call, pass a single argument array to orderBy

{
  collection: 'cities',
  orderBy: ['state'],
  // orderBy: 'state' // string notation can also be used
},

Multiple orderBys are as simple as passing multiple argument arrays (each one representing a orderBy call)

{
  collection: 'cities',
  orderBy: [
    ['state'],
    ['population', 'desc']
  ]
},

Should only be used with collections

limit

Limit the query to a certain number of results

{
  collection: 'cities',
  limit: 10
},

Should only be used with collections

startAt

Creates a new query where the results start at the provided document (inclusive)

From Firebase's startAt docs

{
  collection: 'cities',
  orderBy: 'population',
  startAt: 1000000
},

Should only be used with collections

startAfter

Creates a new query where the results start after the provided document (exclusive)...

From Firebase's startAfter docs

{
  collection: 'cities',
  orderBy: 'population',
  startAfter: 1000000
},

Should only be used with collections

endAt

Creates a new query where the results end at the provided document (inclusive)...

From Firebase's endAt docs

{
  collection: 'cities',
  orderBy: 'population',
  endAt: 1000000
},

Should only be used with collections

endBefore

Creates a new query where the results end before the provided document (exclusive) ...

From Firebase's endBefore docs

{
  collection: 'cities',
  orderBy: 'population',
  endBefore: 1000000
},

Should only be used with collections

storeAs

Storing data under a different path within redux is as easy as passing the storeAs parameter to your query:

{
  collection: 'cities',
  where: ['state', '==', 'CA'],
  storeAs: 'caliCities' // store data in redux under this path instead of "cities"
},

NOTE: Not yet supported inside of subcollections (only at the top level)

Other Firebase Statics

Other Firebase statics (such as FieldValue) are available through the firestore instance:

import { connect } from 'react-redux'
import {
  compose,
  withHandlers,
  lifecycle,
  withContext,
  getContext
} from 'recompose'

const withFirestore = compose(
  withContext({ store: PropTypes.object }, () => {}),
  getContext({ store: PropTypes.object }),
)

const enhance = compose(
  withStore,
  withHandlers({
    onDoneClick: props => (key, done = true) => {
      const { firestore } = props.store
      return firestore.update(`todos/${key}`, {
        done,
        updatedAt: firestore.FieldValue.serverTimestamp() // use static from firestore instance
      }),
    }
  })
)

export default enhance(SomeComponent)

Config Options

enableLogging

Default: false

Whether or not to enable Firebase client logging.

logListenerError

Default: true

Whether or not to use console.error to log listener error objects. Errors from listeners are helpful to developers on multiple occasions including when index needs to be added.

enhancerNamespace

Default: 'firestore'

Namespace under which enhancer places internal instance on redux store (i.e. store.firestore).

allowMultipleListeners

Default: false

Whether or not to allow multiple listeners to be attached for the same query. If a function is passed the arguments it receives are listenerToAttach, currentListeners, and the function should return a boolean.

oneListenerPerPath

Default: false

If set to true redux-firestore will attach a listener on the same path just once & will count how many the listener was set. When you try to unset the lisnter, it won't unset until you have less than 1 listeners on this path

preserveOnDelete

Default: null

Values to preserve from state when DELETE_SUCCESS action is dispatched. Note that this will not prevent the LISTENER_RESPONSE action from removing items from state.ordered if you have a listener attached.

preserveOnListenerError

Default: null

Values to preserve from state when LISTENER_ERROR action is dispatched.

onAttemptCollectionDelete

Default: null

Arguments:(queryOption, dispatch, firebase)

Function run when attempting to delete a collection. If not provided (default) delete promise will be rejected with "Only documents can be deleted" unless. This is due to the fact that Collections can not be deleted from a client, it should instead be handled within a cloud function (which can be called by providing a promise to onAttemptCollectionDelete that calls the cloud function).

mergeOrdered

Default: true

Whether or not to merge data within orderedReducer.

mergeOrderedDocUpdate

Default: true

Whether or not to merge data from document listener updates within orderedReducer.

mergeOrderedCollectionUpdates

Default: true

Whether or not to merge data from collection listener updates within orderedReducer.

Builds

Most commonly people consume Redux Firestore as a CommonJS module. This module is what you get when you import redux in a Webpack, Browserify, or a Node environment.

If you don't use a module bundler, it's also fine. The redux-firestore npm package includes precompiled production and development UMD builds in the dist folder. They can be used directly without a bundler and are thus compatible with many popular JavaScript module loaders and environments. For example, you can drop a UMD build as a <script> tag on the page. The UMD builds make Redux Firestore available as a window.ReduxFirestore global variable.

It can be imported like so:

<script src="../node_modules/redux-firestore/dist/redux-firestore.min.js"></script>
<!-- or through cdn: <script src="https://unpkg.com/redux-firestore@latest/dist/redux-firestore.min.js"></script> -->
<script>console.log('redux firestore:', window.ReduxFirestore)</script>

Note: In an effort to keep things simple, the wording from this explanation was modeled after the installation section of the Redux Docs.

Applications Using This

FAQ

  1. How do I update a document within a subcollection?

    Provide subcollections config the same way you do while querying:

    props.firestore.update(
      {
        collection: 'cities',
        doc: 'SF',
        subcollections: [{ collection: 'counties', doc: 'San Mateo' }],
      },
      { some: 'changes' }
    );
  2. How do I get auth state in redux?

    You will most likely want to use react-redux-firebase or another redux/firebase connector. For more information please visit the complementary package section.

  3. Are there Higher Order Components for use with React?

    react-redux-firebase contains firebaseConnect, firestoreConnect, withFirebase and withFirestore HOCs. For more information please visit the complementary package section.

Roadmap

  • Automatic support for documents that have a parameter and a subcollection with the same name (currently requires storeAs)
  • Support for Passing a Ref to setListener in place of queryConfig object or string

Post an issue with a feature suggestion if you have any ideas!

redux-firestore's People

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.