1 | # redux-firestore
|
2 |
|
3 | [![NPM version][npm-image]][npm-url]
|
4 | [![NPM downloads][npm-downloads-image]][npm-url]
|
5 | [![License][license-image]][license-url]
|
6 | [![Code Style][code-style-image]][code-style-url]
|
7 | [![Dependency Status][daviddm-image]][daviddm-url]
|
8 | [![Build Status][travis-image]][travis-url]
|
9 | [![Code Coverage][coverage-image]][coverage-url]
|
10 |
|
11 | [![Gitter][gitter-image]][gitter-url]
|
12 |
|
13 |
|
14 | > Redux bindings for Firestore. Provides low-level API used in other libraries such as [react-redux-firebase](https://github.com/prescottprue/react-redux-firebase)
|
15 |
|
16 | ## Installation
|
17 |
|
18 | ```sh
|
19 | npm install redux-firestore --save
|
20 | ```
|
21 |
|
22 | This assumes you are using [npm](https://www.npmjs.com/) as your package manager.
|
23 |
|
24 | If you're not, you can access the library on [unpkg](https://unpkg.com/redux-firestore@latest/dist/redux-firestore.min.js), download it, or point your package manager to it. Theres more on this in the [Builds section below](#builds)
|
25 |
|
26 | ## Complementary Package
|
27 |
|
28 | Most likely, you'll want react bindings, for that you will need [react-redux-firebase](https://github.com/prescottprue/react-redux-firebase). You can install the current version it by running:
|
29 |
|
30 | ```sh
|
31 | npm install --save react-redux-firebase
|
32 | ```
|
33 |
|
34 | [react-redux-firebase](https://github.com/prescottprue/react-redux-firebase) provides [`withFirestore`](http://react-redux-firebase.com/docs/api/withFirestore.html) and [`firestoreConnect`](http://react-redux-firebase.com/docs/api/firestoreConnect.html) higher order components, which handle automatically calling `redux-firestore` internally based on component's lifecycle (i.e. mounting/un-mounting)
|
35 |
|
36 | ## Use
|
37 |
|
38 | ```javascript
|
39 | import { createStore, combineReducers, compose } from 'redux'
|
40 | import { reduxFirestore, firestoreReducer } from 'redux-firestore'
|
41 | import firebase from 'firebase/app'
|
42 | import 'firebase/auth'
|
43 | import 'firebase/database'
|
44 | import 'firebase/firestore'
|
45 |
|
46 | const firebaseConfig = {} // from Firebase Console
|
47 |
|
48 | // Initialize firebase instance
|
49 | firebase.initializeApp(firebaseConfig)
|
50 | // Initialize Cloud Firestore through Firebase
|
51 | firebase.firestore();
|
52 |
|
53 | // Add reduxFirestore store enhancer to store creator
|
54 | const createStoreWithFirebase = compose(
|
55 | reduxFirestore(firebase), // firebase instance as first argument
|
56 | )(createStore)
|
57 |
|
58 | // Add Firebase to reducers
|
59 | const rootReducer = combineReducers({
|
60 | firestore: firestoreReducer
|
61 | })
|
62 |
|
63 | // Create store with reducers and initial state
|
64 | const initialState = {}
|
65 | const store = createStoreWithFirebase(rootReducer, initialState)
|
66 | ```
|
67 |
|
68 | Then pass store to your component's context using [react-redux's `Provider`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#provider-store):
|
69 |
|
70 | ```js
|
71 | import React from 'react';
|
72 | import { render } from 'react-dom';
|
73 | import { Provider } from 'react-redux';
|
74 |
|
75 | render(
|
76 | <Provider store={store}>
|
77 | <MyRootComponent />
|
78 | </Provider>,
|
79 | rootEl
|
80 | )
|
81 | ```
|
82 |
|
83 | ### Call Firestore
|
84 |
|
85 | #### Firestore Instance
|
86 |
|
87 | ##### Functional Components
|
88 |
|
89 | 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](https://github.com/acdlite/recompose/blob/master/docs/API.md) by providing Higher Order Component functions such as `withContext`, `lifecycle`, and `withHandlers`.
|
90 |
|
91 | ```js
|
92 | import { connect } from 'react-redux'
|
93 | import {
|
94 | compose,
|
95 | withHandlers,
|
96 | lifecycle,
|
97 | withContext,
|
98 | getContext
|
99 | } from 'recompose'
|
100 |
|
101 | const withStore = compose(
|
102 | withContext({ store: PropTypes.object }, () => {}),
|
103 | getContext({ store: PropTypes.object }),
|
104 | )
|
105 |
|
106 | const enhance = compose(
|
107 | withStore,
|
108 | withHandlers({
|
109 | loadData: props => () => props.store.firestore.get('todos'),
|
110 | onDoneClick: props => (key, done = false) =>
|
111 | props.store.firestore.update(`todos/${key}`, { done }),
|
112 | onNewSubmit: props => newTodo =>
|
113 | props.store.firestore.add('todos', { ...newTodo, owner: 'Anonymous' }),
|
114 | }),
|
115 | lifecycle({
|
116 | componentWillMount(props) {
|
117 | props.loadData()
|
118 | }
|
119 | }),
|
120 | connect(({ firebase }) => ({ // state.firebase
|
121 | todos: firebase.ordered.todos,
|
122 | }))
|
123 | )
|
124 |
|
125 | export default enhance(SomeComponent)
|
126 | ```
|
127 |
|
128 | For more information [on using recompose visit the docs](https://github.com/acdlite/recompose/blob/master/docs/API.md)
|
129 |
|
130 | ##### Component Class
|
131 |
|
132 | ```js
|
133 | import React, { Component } from 'react'
|
134 | import PropTypes from 'prop-types'
|
135 | import { connect } from 'react-redux'
|
136 | import { watchEvents, unWatchEvents } from './actions/query'
|
137 | import { getEventsFromInput, createCallable } from './utils'
|
138 |
|
139 | class Todos extends Component {
|
140 | static contextTypes = {
|
141 | store: PropTypes.object.isRequired
|
142 | }
|
143 |
|
144 | componentWillMount () {
|
145 | const { firestore } = this.context.store
|
146 | firestore.get('todos')
|
147 | }
|
148 |
|
149 | render () {
|
150 | return (
|
151 | <div>
|
152 | {
|
153 | todos.map(todo => (
|
154 | <div key={todo.id}>
|
155 | {JSON.stringify(todo)}
|
156 | </div>
|
157 | ))
|
158 | }
|
159 | </div>
|
160 | )
|
161 | }
|
162 | }
|
163 |
|
164 | export default connect((state) => ({
|
165 | todos: state.firestore.ordered.todos
|
166 | }))(Todos)
|
167 | ```
|
168 | ### API
|
169 | The `store.firestore` instance created by the `reduxFirestore` enhancer extends [Firebase's JS API for Firestore](https://firebase.google.com/docs/reference/js/firebase.firestore). This means all of the methods regularly available through `firebase.firestore()` and the statics available from `firebase.firestore` are available. Certain methods (such as `get`, `set`, and `onSnapshot`) have a different API since they have been extended with action dispatching. The methods which have dispatch actions are listed below:
|
170 |
|
171 | #### Actions
|
172 |
|
173 | ##### get
|
174 | ```js
|
175 | store.firestore.get({ collection: 'cities' }),
|
176 | // store.firestore.get({ collection: 'cities', doc: 'SF' }), // doc
|
177 | ```
|
178 |
|
179 | ##### set
|
180 | ```js
|
181 | store.firestore.set({ collection: 'cities', doc: 'SF' }, { name: 'San Francisco' }),
|
182 | ```
|
183 |
|
184 | ##### add
|
185 | ```js
|
186 | store.firestore.add({ collection: 'cities' }, { name: 'Some Place' }),
|
187 | ```
|
188 |
|
189 | ##### update
|
190 | ```js
|
191 | const itemUpdates = {
|
192 | some: 'value',
|
193 | updatedAt: store.firestore.FieldValue.serverTimestamp()
|
194 | }
|
195 |
|
196 | store.firestore.update({ collection: 'cities', doc: 'SF' }, itemUpdates),
|
197 | ```
|
198 |
|
199 | ##### delete
|
200 | ```js
|
201 | store.firestore.delete({ collection: 'cities', doc: 'SF' }),
|
202 | ```
|
203 |
|
204 | ##### runTransaction
|
205 | ```js
|
206 | store.firestore.runTransaction(t => {
|
207 | return t.get(cityRef)
|
208 | .then(doc => {
|
209 | // Add one person to the city population
|
210 | const newPopulation = doc.data().population + 1;
|
211 | t.update(cityRef, { population: newPopulation });
|
212 | });
|
213 | })
|
214 | .then(result => {
|
215 | // TRANSACTION_SUCCESS action dispatched
|
216 | console.log('Transaction success!');
|
217 | }).catch(err => {
|
218 | // TRANSACTION_FAILURE action dispatched
|
219 | console.log('Transaction failure:', err);
|
220 | });
|
221 | ```
|
222 |
|
223 | #### Types of Queries
|
224 |
|
225 | ##### get
|
226 | ```js
|
227 | props.store.firestore.get({ collection: 'cities' }),
|
228 | // store.firestore.get({ collection: 'cities', doc: 'SF' }), // doc
|
229 | ```
|
230 |
|
231 | ##### onSnapshot/setListener
|
232 |
|
233 | ```js
|
234 | store.firestore.onSnapshot({ collection: 'cities' }),
|
235 | // store.firestore.setListener({ collection: 'cities' }), // alias
|
236 | // store.firestore.setListener({ collection: 'cities', doc: 'SF' }), // doc
|
237 | ```
|
238 |
|
239 | #### setListeners
|
240 |
|
241 | ```js
|
242 | store.firestore.setListeners([
|
243 | { collection: 'cities' },
|
244 | { collection: 'users' },
|
245 | ]),
|
246 | ```
|
247 |
|
248 | #### Query Options
|
249 |
|
250 | ##### Collection
|
251 | ```js
|
252 | { collection: 'cities' },
|
253 | // or string equivalent
|
254 | // store.firestore.get('cities'),
|
255 | ```
|
256 |
|
257 | ##### Document
|
258 |
|
259 | ```js
|
260 | { collection: 'cities', doc: 'SF' },
|
261 | // or string equivalent
|
262 | // props.store.firestore.get('cities/SF'),
|
263 | ```
|
264 |
|
265 | ##### Sub Collections
|
266 |
|
267 | ```js
|
268 | { collection: 'cities', doc: 'SF', subcollections: [{ collection: 'zipcodes' }] },
|
269 | // or string equivalent
|
270 | // props.store.firestore.get('cities/SF'),
|
271 | ```
|
272 |
|
273 | **Note:** When nesting sub-collections, [`storeAs`](#storeas) should be used for more optimal state updates.
|
274 |
|
275 | ##### Where
|
276 |
|
277 | To create a single `where` call, pass a single argument array to the `where` parameter:
|
278 |
|
279 | ```js
|
280 | {
|
281 | collection: 'cities',
|
282 | where: ['state', '==', 'CA']
|
283 | },
|
284 | ```
|
285 |
|
286 | Multiple `where` queries are as simple as passing multiple argument arrays (each one representing a `where` call):
|
287 |
|
288 | ```js
|
289 | {
|
290 | collection: 'cities',
|
291 | where: [
|
292 | ['state', '==', 'CA'],
|
293 | ['population', '<', 100000]
|
294 | ]
|
295 | },
|
296 | ```
|
297 |
|
298 | *Can only be used with collections*
|
299 |
|
300 | ##### orderBy
|
301 |
|
302 | To create a single `orderBy` call, pass a single argument array to `orderBy`
|
303 |
|
304 | ```js
|
305 | {
|
306 | collection: 'cities',
|
307 | orderBy: ['state'],
|
308 | // orderBy: 'state' // string notation can also be used
|
309 | },
|
310 | ```
|
311 |
|
312 | Multiple `orderBy`s are as simple as passing multiple argument arrays (each one representing a `orderBy` call)
|
313 |
|
314 | ```js
|
315 | {
|
316 | collection: 'cities',
|
317 | orderBy: [
|
318 | ['state'],
|
319 | ['population', 'desc']
|
320 | ]
|
321 | },
|
322 | ```
|
323 |
|
324 | *Can only be used with collections*
|
325 |
|
326 | ##### limit
|
327 |
|
328 | Limit the query to a certain number of results
|
329 |
|
330 | ```js
|
331 | {
|
332 | collection: 'cities',
|
333 | limit: 10
|
334 | },
|
335 | ```
|
336 |
|
337 | *Can only be used with collections*
|
338 |
|
339 | ##### startAt
|
340 |
|
341 | > Creates a new query where the results start at the provided document (inclusive)
|
342 |
|
343 | [From Firebase's `startAt` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#startAt)
|
344 |
|
345 | ```js
|
346 | {
|
347 | collection: 'cities',
|
348 | orderBy: 'population',
|
349 | startAt: 1000000
|
350 | },
|
351 | ```
|
352 |
|
353 | *Can only be used with collections*
|
354 |
|
355 | ##### startAfter
|
356 |
|
357 | > Creates a new query where the results start after the provided document (exclusive)...
|
358 |
|
359 | [From Firebase's `startAfter` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#startAfter)
|
360 |
|
361 | ```js
|
362 | {
|
363 | collection: 'cities',
|
364 | orderBy: 'population',
|
365 | startAfter: 1000000
|
366 | },
|
367 | ```
|
368 |
|
369 | *Can only be used with collections*
|
370 |
|
371 | ##### endAt
|
372 |
|
373 | > Creates a new query where the results end at the provided document (inclusive)...
|
374 |
|
375 | [From Firebase's `endAt` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#endAt)
|
376 |
|
377 |
|
378 | ```js
|
379 | {
|
380 | collection: 'cities',
|
381 | orderBy: 'population',
|
382 | endAt: 1000000
|
383 | },
|
384 | ```
|
385 |
|
386 | *Can only be used with collections*
|
387 |
|
388 | ##### endBefore
|
389 |
|
390 | > Creates a new query where the results end before the provided document (exclusive) ...
|
391 |
|
392 | [From Firebase's `endBefore` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#endBefore)
|
393 |
|
394 |
|
395 | ```js
|
396 | {
|
397 | collection: 'cities',
|
398 | orderBy: 'population',
|
399 | endBefore: 1000000
|
400 | },
|
401 | ```
|
402 |
|
403 | *Can only be used with collections*
|
404 |
|
405 | ##### storeAs
|
406 |
|
407 | Storing data under a different path within redux is as easy as passing the `storeAs` parameter to your query:
|
408 |
|
409 | ```js
|
410 | {
|
411 | collection: 'cities',
|
412 | where: ['state', '==', 'CA'],
|
413 | storeAs: 'caliCities' // store data in redux under this path instead of "cities"
|
414 | },
|
415 | ```
|
416 |
|
417 | **NOTE:** Usage of `"/"` and `"."` within `storeAs` can cause unexpected behavior when attempting to retrieve from redux state
|
418 |
|
419 |
|
420 | #### Other Firebase Statics
|
421 |
|
422 | Other Firebase statics (such as [FieldValue](https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue)) are available through the firestore instance:
|
423 |
|
424 | ```js
|
425 | import { connect } from 'react-redux'
|
426 | import {
|
427 | compose,
|
428 | withHandlers,
|
429 | lifecycle,
|
430 | withContext,
|
431 | getContext
|
432 | } from 'recompose'
|
433 |
|
434 | const withFirestore = compose(
|
435 | withContext({ store: PropTypes.object }, () => {}),
|
436 | getContext({ store: PropTypes.object }),
|
437 | )
|
438 |
|
439 | const enhance = compose(
|
440 | withStore,
|
441 | withHandlers({
|
442 | onDoneClick: props => (key, done = true) => {
|
443 | const { firestore } = props.store
|
444 | return firestore.update(`todos/${key}`, {
|
445 | done,
|
446 | updatedAt: firestore.FieldValue.serverTimestamp() // use static from firestore instance
|
447 | }),
|
448 | }
|
449 | })
|
450 | )
|
451 |
|
452 | export default enhance(SomeComponent)
|
453 | ```
|
454 |
|
455 | ## Config Options
|
456 |
|
457 | #### logListenerError
|
458 | Default: `true`
|
459 |
|
460 | 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.
|
461 |
|
462 | #### enhancerNamespace
|
463 | Default: `'firestore'`
|
464 |
|
465 | Namespace under which enhancer places internal instance on redux store (i.e. `store.firestore`).
|
466 |
|
467 | #### allowMultipleListeners
|
468 | Default: `false`
|
469 |
|
470 | 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.
|
471 |
|
472 | #### oneListenerPerPath
|
473 | Default: `false`
|
474 |
|
475 | 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
|
476 |
|
477 | #### preserveOnDelete
|
478 | Default: `null`
|
479 |
|
480 | 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.
|
481 |
|
482 | #### preserveOnListenerError
|
483 | Default: `null`
|
484 |
|
485 | Values to preserve from state when LISTENER_ERROR action is dispatched.
|
486 |
|
487 | #### onAttemptCollectionDelete
|
488 | Default: `null`
|
489 |
|
490 | Arguments:`(queryOption, dispatch, firebase)`
|
491 |
|
492 | 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).
|
493 |
|
494 | #### mergeOrdered
|
495 | Default: `true`
|
496 |
|
497 | Whether or not to merge data within `orderedReducer`.
|
498 |
|
499 | #### mergeOrderedDocUpdate
|
500 | Default: `true`
|
501 |
|
502 | Whether or not to merge data from document listener updates within `orderedReducer`.
|
503 |
|
504 |
|
505 | #### mergeOrderedCollectionUpdates
|
506 | Default: `true`
|
507 |
|
508 | Whether or not to merge data from collection listener updates within `orderedReducer`.
|
509 |
|
510 |
|
511 |
|
512 | `redux-firestore`'s enhancer offers a new middleware setup that was not offered in `react-redux-firebase` (but will eventually make it `redux-firebase`)
|
513 | **Note**: This syntax is just a sample and is not currently released
|
514 |
|
515 | ##### Setup
|
516 | ```js
|
517 | ```
|
518 |
|
519 |
|
520 | ##### Usage
|
521 |
|
522 | ```js
|
523 | import { FIREBASE_CALL } from 'redux-firestore'
|
524 |
|
525 | dispatch({
|
526 | type: FIREBASE_CALL,
|
527 | collection: 'users', // only used when namespace is firestore
|
528 | method: 'get' // get method
|
529 | })
|
530 | ```
|
531 |
|
532 | Some of the goals behind this approach include:
|
533 |
|
534 | 1. Not needing to pass around a Firebase instance (with `react-redux-firebase` this meant using `firebaseConnect` HOC or `getFirebase`)
|
535 | 2. Follows [patterns outlined in the redux docs for data fetching](http://redux.js.org/docs/advanced/ExampleRedditAPI.html)
|
536 | 3. Easier to expand/change internal API as Firebase/Firestore API grows & changes -->
|
537 |
|
538 | ## Builds
|
539 |
|
540 | Most commonly people consume Redux Firestore as a [CommonJS module](http://webpack.github.io/docs/commonjs.html). This module is what you get when you import redux in a Webpack, Browserify, or a Node environment.
|
541 |
|
542 | If you don't use a module bundler, it's also fine. The redux-firestore npm package includes precompiled production and development [UMD builds](https://github.com/umdjs/umd) in the [dist folder](https://unpkg.com/redux-firestore@latest/dist/). 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.
|
543 |
|
544 | It can be imported like so:
|
545 |
|
546 | ```html
|
547 | <script src="../node_modules/redux-firestore/dist/redux-firestore.min.js"></script>
|
548 | <!-- or through cdn: <script src="https://unpkg.com/redux-firestore@latest/dist/redux-firestore.min.js"></script> -->
|
549 | <script>console.log('redux firestore:', window.ReduxFirestore)</script>
|
550 | ```
|
551 |
|
552 | Note: In an effort to keep things simple, the wording from this explanation was modeled after [the installation section of the Redux Docs](https://redux.js.org/#installation).
|
553 |
|
554 | ## Applications Using This
|
555 | * [fireadmin.io](http://fireadmin.io) - Firebase Instance Management Tool (source [available here](https://github.com/prescottprue/fireadmin))
|
556 |
|
557 | ## FAQ
|
558 | 1. How do I update a document within a subcollection?
|
559 |
|
560 | Provide `subcollections` config the same way you do while querying:
|
561 |
|
562 | ```js
|
563 | props.firestore.update(
|
564 | {
|
565 | collection: 'cities',
|
566 | doc: 'SF',
|
567 | subcollections: [{ collection: 'counties', doc: 'San Mateo' }],
|
568 | },
|
569 | { some: 'changes' }
|
570 | );
|
571 | ```
|
572 |
|
573 | 1. How do I get auth state in redux?
|
574 |
|
575 | You will most likely want to use [`react-redux-firebase`](https://github.com/prescottprue/react-redux-firebase) or another redux/firebase connector. For more information please visit the [complementary package section](#complementary-package).
|
576 |
|
577 | 1. Are there Higher Order Components for use with React?
|
578 |
|
579 | [`react-redux-firebase`](https://github.com/prescottprue/react-redux-firebase) contains `firebaseConnect`, `firestoreConnect`, `withFirebase` and `withFirestore` HOCs. For more information please visit the [complementary package section](#complementary-package).
|
580 |
|
581 | ## Roadmap
|
582 |
|
583 | * Automatic support for documents that have a parameter and a subcollection with the same name (currently requires `storeAs`)
|
584 | * Support for Passing a Ref to `setListener` in place of `queryConfig` object or string
|
585 |
|
586 | Post an issue with a feature suggestion if you have any ideas!
|
587 |
|
588 | [npm-image]: https://img.shields.io/npm/v/redux-firestore.svg?style=flat-square
|
589 | [npm-url]: https://npmjs.org/package/redux-firestore
|
590 | [npm-downloads-image]: https://img.shields.io/npm/dm/redux-firestore.svg?style=flat-square
|
591 | [quality-image]: http://npm.packagequality.com/shield/redux-firestore.svg?style=flat-square
|
592 | [quality-url]: https://packagequality.com/#?package=redux-firestore
|
593 | [travis-image]: https://img.shields.io/travis/prescottprue/redux-firestore/master.svg?style=flat-square
|
594 | [travis-url]: https://travis-ci.org/prescottprue/redux-firestore
|
595 | [daviddm-image]: https://img.shields.io/david/prescottprue/redux-firestore.svg?style=flat-square
|
596 | [daviddm-url]: https://david-dm.org/prescottprue/redux-firestore
|
597 | [climate-image]: https://img.shields.io/codeclimate/github/prescottprue/redux-firestore.svg?style=flat-square
|
598 | [climate-url]: https://codeclimate.com/github/prescottprue/redux-firestore
|
599 | [coverage-image]: https://img.shields.io/codecov/c/github/prescottprue/redux-firestore.svg?style=flat-square
|
600 | [coverage-url]: https://codecov.io/gh/prescottprue/redux-firestore
|
601 | [license-image]: https://img.shields.io/npm/l/redux-firestore.svg?style=flat-square
|
602 | [license-url]: https://github.com/prescottprue/redux-firestore/blob/master/LICENSE
|
603 | [code-style-image]: https://img.shields.io/badge/code%20style-airbnb-blue.svg?style=flat-square
|
604 | [code-style-url]: https://github.com/airbnb/javascript
|
605 | [gitter-image]: https://img.shields.io/gitter/room/redux-firestore/gitter.svg?style=flat-square
|
606 | [gitter-url]: https://gitter.im/redux-firestore/Lobby
|