From f0eaada7c5bc437ee7e2d75f0027da5e9ca9484e Mon Sep 17 00:00:00 2001 From: Richard Scotten Date: Sun, 23 Feb 2020 10:52:40 -0800 Subject: [PATCH 1/3] fixed query cursors to allow for multiple fields and added tests --- src/utils/query.js | 33 +++++++++++---- test/unit/utils/query.spec.js | 77 ++++++++++++++++++++++++++++++++--- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/src/utils/query.js b/src/utils/query.js index 730c1e23..c0acea95 100644 --- a/src/utils/query.js +++ b/src/utils/query.js @@ -74,6 +74,16 @@ function addOrderByToRef(ref, orderBy) { ); } +/** + * Convert cursor into a string array for spreading into cursor functions + * @see https://firebase.google.com/docs/firestore/query-data/query-cursors#set_cursor_based_on_multiple_fields + * @param {Array|string} cursor - The cursor + * @returns {Array} String array + */ +function arrayify(cursor) { + return [].concat(cursor); +} + /** * Call methods on ref object for provided subcollection list (from queryConfig * object) @@ -102,13 +112,18 @@ function handleSubcollections(ref, subcollectionList) { ref = addOrderByToRef(ref, subcollection.orderBy); } if (subcollection.limit) ref = ref.limit(subcollection.limit); - if (subcollection.startAt) ref = ref.startAt(subcollection.startAt); + if (subcollection.startAt) { + ref = ref.startAt(...arrayify(subcollection.startAt)); + } if (subcollection.startAfter) { - ref = ref.startAfter(subcollection.startAfter); + ref = ref.startAfter(...arrayify(subcollection.startAfter)); + } + if (subcollection.endAt) { + ref = ref.endAt(...arrayify(subcollection.endAt)); + } + if (subcollection.endBefore) { + ref = ref.endBefore(...arrayify(subcollection.endBefore)); } - if (subcollection.endAt) ref = ref.endAt(subcollection.endAt); - if (subcollection.endBefore) ref = ref.endBefore(subcollection.endBefore); - ref = handleSubcollections(ref, subcollection.subcollections); /* eslint-enable */ }); @@ -159,10 +174,10 @@ export function firestoreRef(firebase, meta) { if (where) ref = addWhereToRef(ref, where); if (orderBy) ref = addOrderByToRef(ref, orderBy); if (limit) ref = ref.limit(limit); - if (startAt) ref = ref.startAt(startAt); - if (startAfter) ref = ref.startAfter(startAfter); - if (endAt) ref = ref.endAt(endAt); - if (endBefore) ref = ref.endBefore(endBefore); + if (startAt) ref = ref.startAt(...arrayify(startAt)); + if (startAfter) ref = ref.startAfter(...arrayify(startAfter)); + if (endAt) ref = ref.endAt(...arrayify(endAt)); + if (endBefore) ref = ref.endBefore(...arrayify(endBefore)); return ref; } diff --git a/test/unit/utils/query.spec.js b/test/unit/utils/query.spec.js index 87b0022d..be97a463 100644 --- a/test/unit/utils/query.spec.js +++ b/test/unit/utils/query.spec.js @@ -77,7 +77,7 @@ describe('query utils', () => { expect(result).to.equal(`${meta.collection}/${meta.doc}`); }); - describe('where paremeter', () => { + describe('where parameter', () => { it('is appended if valid', () => { meta = { collection: 'test', doc: 'doc', where: 'some' }; expect(() => getQueryName(meta)).to.throw( @@ -101,7 +101,7 @@ describe('query utils', () => { }); }); - describe('limit paremeter', () => { + describe('limit parameter', () => { it('is appended if valid', () => { meta = { collection: 'test', @@ -109,24 +109,39 @@ describe('query utils', () => { limit: 10, }; result = getQueryName(meta); + expect(result).to.equal('test/doc?limit=10'); }); }); - describe('startAt paremeter', () => { - it('is appended if valid', () => { + describe('startAt parameter', () => { + it('is appended if valid string', () => { meta = { collection: 'test', startAt: 'asdf', }; result = getQueryName(meta); + expect(result).to.equal('test?startAt=asdf'); }); - it('appends passed date objects (#186)', () => { + it('is appended if valid array', () => { meta = { collection: 'test', - startAt: new Date(), + startAt: ['asdf', 1234, 'qwerty'], }; result = getQueryName(meta); + expect(result).to.equal('test?startAt=asdf:1234:qwerty'); + }); + + it('appends passed date objects', () => { + meta = { + collection: 'test', + startAt: new Date(2020, 2, 2, 2, 2, 2), + }; + result = getQueryName(meta).substr(0, 37); + // Using .substr() to drop the GMT timezone + // since stringifying a Date object uses .toGMTString() + // and the local timezone instead of .toUTCString() + expect(result).to.equal('test?startAt=Mon Mar 02 2020 02:02:02'); }); }); }); @@ -727,6 +742,19 @@ describe('query utils', () => { expect(result).to.be.an('object'); expect(startAtSpy).to.be.calledWith(meta.startAt); }); + + it('calls startAt if valid array', () => { + meta = { collection: 'test', startAt: ['other', 'another'] }; + const startAtSpy = sinon.spy(() => ({})); + fakeFirebase = { + firestore: () => ({ + collection: () => ({ startAt: startAtSpy }), + }), + }; + result = firestoreRef(fakeFirebase, meta); + expect(result).to.be.an('object'); + expect(startAtSpy).to.be.calledWith(...meta.startAt); + }); }); describe('startAfter', () => { @@ -742,6 +770,19 @@ describe('query utils', () => { expect(result).to.be.an('object'); expect(startAfterSpy).to.be.calledWith(meta.startAfter); }); + + it('calls startAfter if valid array', () => { + meta = { collection: 'test', startAfter: ['other', 'another'] }; + const startAfterSpy = sinon.spy(() => ({})); + fakeFirebase = { + firestore: () => ({ + collection: () => ({ startAfter: startAfterSpy }), + }), + }; + result = firestoreRef(fakeFirebase, meta); + expect(result).to.be.an('object'); + expect(startAfterSpy).to.be.calledWith(...meta.startAfter); + }); }); describe('endAt', () => { @@ -755,6 +796,17 @@ describe('query utils', () => { expect(result).to.be.an('object'); expect(endAtSpy).to.be.calledWith(meta.endAt); }); + + it('calls endAt if valid array', () => { + meta = { collection: 'test', endAt: ['other', 'another'] }; + const endAtSpy = sinon.spy(() => ({})); + fakeFirebase = { + firestore: () => ({ collection: () => ({ endAt: endAtSpy }) }), + }; + result = firestoreRef(fakeFirebase, meta); + expect(result).to.be.an('object'); + expect(endAtSpy).to.be.calledWith(...meta.endAt); + }); }); describe('endBefore', () => { @@ -770,6 +822,19 @@ describe('query utils', () => { expect(result).to.be.an('object'); expect(endBeforeSpy).to.be.calledWith(meta.endBefore); }); + + it('calls endBefore if valid array', () => { + meta = { collection: 'test', endBefore: ['other', 'another'] }; + const endBeforeSpy = sinon.spy(() => ({})); + fakeFirebase = { + firestore: () => ({ + collection: () => ({ endBefore: endBeforeSpy }), + }), + }; + result = firestoreRef(fakeFirebase, meta); + expect(result).to.be.an('object'); + expect(endBeforeSpy).to.be.calledWith(...meta.endBefore); + }); }); }); From 04c54ea9072999cabb980529297fee04c8b4a4c2 Mon Sep 17 00:00:00 2001 From: Richard Scotten Date: Sun, 23 Feb 2020 11:03:03 -0800 Subject: [PATCH 2/3] improved function comments --- src/utils/query.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/query.js b/src/utils/query.js index c0acea95..07557465 100644 --- a/src/utils/query.js +++ b/src/utils/query.js @@ -77,8 +77,8 @@ function addOrderByToRef(ref, orderBy) { /** * Convert cursor into a string array for spreading into cursor functions * @see https://firebase.google.com/docs/firestore/query-data/query-cursors#set_cursor_based_on_multiple_fields - * @param {Array|string} cursor - The cursor - * @returns {Array} String array + * @param {Array|string} cursor - The cursor as a string or string array + * @returns {Array} String Array - The cursor as a string array */ function arrayify(cursor) { return [].concat(cursor); From 6552b396f596c9b8b51ea403eb686bc5dccfe456 Mon Sep 17 00:00:00 2001 From: Richard Scotten Date: Sun, 23 Feb 2020 11:06:56 -0800 Subject: [PATCH 3/3] updated readme --- README.md | 283 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 156 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 8da13d94..112992cb 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Code Coverage][coverage-image]][coverage-url] [![Gitter][gitter-image]][gitter-url] + > Redux bindings for Firestore. Provides low-level API used in other libraries such as [react-redux-firebase](https://github.com/prescottprue/react-redux-firebase) @@ -36,34 +37,34 @@ npm install --save react-redux-firebase ## Use ```javascript -import { createStore, combineReducers, compose } from 'redux' -import { reduxFirestore, firestoreReducer } from 'redux-firestore' -import firebase from 'firebase/app' -import 'firebase/auth' -import 'firebase/database' -import 'firebase/firestore' +import { createStore, combineReducers, compose } from 'redux'; +import { reduxFirestore, firestoreReducer } from 'redux-firestore'; +import firebase from 'firebase/app'; +import 'firebase/auth'; +import 'firebase/database'; +import 'firebase/firestore'; -const firebaseConfig = {} // from Firebase Console -const rfConfig = {} // optional redux-firestore Config Options +const firebaseConfig = {}; // from Firebase Console +const rfConfig = {}; // optional redux-firestore Config Options // Initialize firebase instance -firebase.initializeApp(firebaseConfig) +firebase.initializeApp(firebaseConfig); // Initialize Cloud Firestore through Firebase firebase.firestore(); // Add reduxFirestore store enhancer to store creator const createStoreWithFirebase = compose( reduxFirestore(firebase, rfConfig), // firebase instance as first argument, rfConfig as optional second -)(createStore) +)(createStore); // Add Firebase to reducers const rootReducer = combineReducers({ - firestore: firestoreReducer -}) + firestore: firestoreReducer, +}); // Create store with reducers and initial state -const initialState = {} -const store = createStoreWithFirebase(rootReducer, initialState) +const initialState = {}; +const store = createStoreWithFirebase(rootReducer, initialState); ``` 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): @@ -77,8 +78,8 @@ render( , - rootEl -) + rootEl, +); ``` ### Call Firestore @@ -90,19 +91,19 @@ render( 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`. ```js -import { connect } from 'react-redux' +import { connect } from 'react-redux'; import { compose, withHandlers, lifecycle, withContext, - getContext -} from 'recompose' + getContext, +} from 'recompose'; const withStore = compose( withContext({ store: PropTypes.object }, () => {}), getContext({ store: PropTypes.object }), -) +); const enhance = compose( withStore, @@ -115,15 +116,16 @@ const enhance = compose( }), lifecycle({ componentDidMount(props) { - props.loadData() - } + props.loadData(); + }, }), - connect(({ firebase }) => ({ // state.firebase + connect(({ firebase }) => ({ + // state.firebase todos: firebase.ordered.todos, - })) -) + })), +); -export default enhance(SomeComponent) +export default enhance(SomeComponent); ``` For more information [on using recompose visit the docs](https://github.com/acdlite/recompose/blob/master/docs/API.md) @@ -131,63 +133,65 @@ For more information [on using recompose visit the docs](https://github.com/acdl ##### Component Class ```js -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { watchEvents, unWatchEvents } from './actions/query' -import { getEventsFromInput, createCallable } from './utils' +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { watchEvents, unWatchEvents } from './actions/query'; +import { getEventsFromInput, createCallable } from './utils'; class Todos extends Component { static contextTypes = { - store: PropTypes.object.isRequired - } + store: PropTypes.object.isRequired, + }; - componentDidMount () { - const { firestore } = this.context.store - firestore.get('todos') + componentDidMount() { + const { firestore } = this.context.store; + firestore.get('todos'); } - render () { + render() { return (
- { - todos.map(todo => ( -
- {JSON.stringify(todo)} -
- )) - } + {todos.map(todo => ( +
{JSON.stringify(todo)}
+ ))}
- ) + ); } } -export default connect((state) => ({ - todos: state.firestore.ordered.todos -}))(Todos) +export default connect(state => ({ + todos: state.firestore.ordered.todos, +}))(Todos); ``` + ### API + 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: #### Actions ##### get + ```js store.firestore.get({ collection: 'cities' }), // store.firestore.get({ collection: 'cities', doc: 'SF' }), // doc ``` ##### set + ```js store.firestore.set({ collection: 'cities', doc: 'SF' }, { name: 'San Francisco' }), ``` ##### add + ```js store.firestore.add({ collection: 'cities' }, { name: 'Some Place' }), ``` ##### update + ```js const itemUpdates = { some: 'value', @@ -198,32 +202,38 @@ store.firestore.update({ collection: 'cities', doc: 'SF' }, itemUpdates), ``` ##### delete + ```js store.firestore.delete({ collection: 'cities', doc: 'SF' }), ``` ##### runTransaction + ```js -store.firestore.runTransaction(t => { - return t.get(cityRef) - .then(doc => { - // Add one person to the city population - const newPopulation = doc.data().population + 1; - t.update(cityRef, { population: newPopulation }); - }); -}) -.then(result => { - // TRANSACTION_SUCCESS action dispatched - console.log('Transaction success!'); -}).catch(err => { - // TRANSACTION_FAILURE action dispatched - console.log('Transaction failure:', err); -}); +store.firestore + .runTransaction(t => { + return t.get(cityRef).then(doc => { + // Add one person to the city population + const newPopulation = doc.data().population + 1; + t.update(cityRef, { population: newPopulation }); + }); + }) + .then(result => { + // TRANSACTION_SUCCESS action dispatched + console.log('Transaction success!'); + }) + .catch(err => { + // TRANSACTION_FAILURE action dispatched + console.log('Transaction failure:', err); + }); ``` #### Types of Queries + Each of these functions take a queryOptions object with options as described in the [Query Options section of this README](#query-options). Some simple query options examples are used here for better comprehension. + ##### get + ```js props.store.firestore.get({ collection: 'cities' }), // store.firestore.get({ collection: 'cities', doc: 'SF' }), // doc @@ -247,7 +257,9 @@ store.firestore.setListeners([ ``` ##### unsetListener / unsetListeners + After setting a listener/multiple listeners, you can unset them with the following two functions. In order to unset a specific listener, you must pass the same queryOptions object given to onSnapshot/setListener(s). + ```js store.firestore.unsetListener({ collection: 'cities' }), // of for any number of listeners at once : @@ -258,6 +270,7 @@ store.firestore.unsetListeners([query1Options, query2Options]), #### Query Options ##### Collection + ```js { collection: 'cities' }, // or string equivalent @@ -286,6 +299,7 @@ store.firestore.unsetListeners([query1Options, query2Options]), **NOTE**: `storeAs` is now required for subcollections. This is to more closley match the logic of [the upcoming major release (v1)](https://github.com/prescottprue/redux-firestore/wiki/v1.0.0-Roadmap) which stores all collections, even subcollections, at the top level of `data` and `ordered` state slices. ##### Collection Group + ```js { collectionGroup: 'landmarks' }, // does not support string equivalent @@ -316,9 +330,9 @@ Multiple `where` queries are as simple as passing multiple argument arrays (each }, ``` -Firestore doesn't alow you to create `or` style queries. Instead, you should pass in multiple queries and compose your data. +Firestore doesn't alow you to create `or` style queries. Instead, you should pass in multiple queries and compose your data. -``` javascript +```javascript ['sally', 'john', 'peter'].map(friendId => ({ collection: 'users', where: [ @@ -329,9 +343,9 @@ Firestore doesn't alow you to create `or` style queries. Instead, you should pa })); ``` -Since the results must be composed, a query like this is unable to be properly ordered. The results should be pulled from `data`. +Since the results must be composed, a query like this is unable to be properly ordered. The results should be pulled from `data`. -*Can only be used with collections* +_Can only be used with collections_ ##### orderBy @@ -357,7 +371,7 @@ Multiple `orderBy`s are as simple as passing multiple argument arrays (each one }, ``` -*Can only be used with collections* +_Can only be used with collections_ ##### limit @@ -370,7 +384,7 @@ Limit the query to a certain number of results }, ``` -*Can only be used with collections* +_Can only be used with collections_ ##### startAt @@ -386,7 +400,7 @@ Limit the query to a certain number of results }, ``` -*Can only be used with collections. Types can be a string, number, or Date object, but not a Firestore Document Snapshot* +_Can only be used with collections. Types can be a string, number, Date object, or an array of these types, but not a Firestore Document Snapshot_ ##### startAfter @@ -402,7 +416,7 @@ Limit the query to a certain number of results }, ``` -*Can only be used with collections. Types can be a string, number, or Date object, but not a Firestore Document Snapshot* +_Can only be used with collections. Types can be a string, number, Date object, or an array of these types, but not a Firestore Document Snapshot_ ##### endAt @@ -410,7 +424,6 @@ Limit the query to a certain number of results [From Firebase's `endAt` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#endAt) - ```js { collection: 'cities', @@ -419,7 +432,7 @@ Limit the query to a certain number of results }, ``` -*Can only be used with collections. Types can be a string, number, or Date object, but not a Firestore Document Snapshot* +_Can only be used with collections. Types can be a string, number, Date object, or an array of these types, but not a Firestore Document Snapshot_ ##### endBefore @@ -427,7 +440,6 @@ Limit the query to a certain number of results [From Firebase's `endBefore` docs](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference#endBefore) - ```js { collection: 'cities', @@ -436,7 +448,7 @@ Limit the query to a certain number of results }, ``` -*Can only be used with collections. Types can be a string, number, or Date object, but not a Firestore Document Snapshot* +_Can only be used with collections. Types can be a string, number, Date object, or an array of these types, but not a Firestore Document Snapshot_ ##### storeAs @@ -452,7 +464,6 @@ Storing data under a different path within redux is as easy as passing the `stor **NOTE:** Usage of `"/"` and `"."` within `storeAs` can cause unexpected behavior when attempting to retrieve from redux state - #### Other Firebase Statics Other Firebase statics (such as [FieldValue](https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue)) are available through the firestore instance: @@ -489,110 +500,122 @@ export default enhance(SomeComponent) ``` ### Population -Population, made popular in [react-redux-firebase](http://react-redux-firebase.com/docs/recipes/populate.html), also works with firestore. +Population, made popular in [react-redux-firebase](http://react-redux-firebase.com/docs/recipes/populate.html), also works with firestore. #### Automatic Listeners + ```js -import { connect } from 'react-redux' -import { firestoreConnect, populate } from 'react-redux-firebase' +import { connect } from 'react-redux'; +import { firestoreConnect, populate } from 'react-redux-firebase'; import { compose, withHandlers, lifecycle, withContext, - getContext -} from 'recompose' + getContext, +} from 'recompose'; -const populates = [{ child: 'createdBy', root: 'users' }] -const collection = 'projects' +const populates = [{ child: 'createdBy', root: 'users' }]; +const collection = 'projects'; const withPopulatedProjects = compose( - firestoreConnect((props) => [ + firestoreConnect(props => [ { collection, - populates - } + populates, + }, ]), connect((state, props) => ({ - projects: populate(state.firestore, collection, populates) - })) -) + projects: populate(state.firestore, collection, populates), + })), +); ``` #### Manually using setListeners + ```js -import { withFirestore, populate } from 'react-redux-firebase' -import { connect } from 'react-redux' -import { compose, lifecycle } from 'recompose' +import { withFirestore, populate } from 'react-redux-firebase'; +import { connect } from 'react-redux'; +import { compose, lifecycle } from 'recompose'; -const collection = 'projects' -const populates = [{ child: 'createdBy', root: 'users' }] +const collection = 'projects'; +const populates = [{ child: 'createdBy', root: 'users' }]; const enhance = compose( withFirestore, lifecycle({ componentDidMount() { - this.props.firestore.setListener({ collection, populates }) - } + this.props.firestore.setListener({ collection, populates }); + }, }), - connect(({ firestore }) => ({ // state.firestore + connect(({ firestore }) => ({ + // state.firestore todos: firestore.ordered.todos, - })) -) + })), +); ``` #### Manually using get + ```js -import { withFirestore, populate } from 'react-redux-firebase' -import { connect } from 'react-redux' -import { compose, lifecycle } from 'recompose' +import { withFirestore, populate } from 'react-redux-firebase'; +import { connect } from 'react-redux'; +import { compose, lifecycle } from 'recompose'; -const collection = 'projects' -const populates = [{ child: 'createdBy', root: 'users' }] +const collection = 'projects'; +const populates = [{ child: 'createdBy', root: 'users' }]; const enhance = compose( withFirestore, lifecycle({ componentDidMount() { - this.props.store.firestore.get({ collection, populates }) - } + this.props.store.firestore.get({ collection, populates }); + }, }), - connect(({ firestore }) => ({ // state.firestore + connect(({ firestore }) => ({ + // state.firestore todos: firestore.ordered.todos, - })) -) + })), +); ``` ## Config Options + Optional configuration options for redux-firestore, provided to reduxFirestore enhancer as optional second argument. Combine any of them together in an object. #### 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. #### 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)` @@ -600,17 +623,19 @@ 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`. @@ -654,42 +679,46 @@ It can be imported like so: ```html - + ``` 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). ## Applications Using This -* [fireadmin.io](http://fireadmin.io) - Firebase Instance Management Tool (source [available here](https://github.com/prescottprue/fireadmin)) + +- [fireadmin.io](http://fireadmin.io) - Firebase Instance Management Tool (source [available here](https://github.com/prescottprue/fireadmin)) ## FAQ + 1. How do I update a document within a subcollection? - Provide `subcollections` config the same way you do while querying: + Provide `subcollections` config the same way you do while querying: - ```js - props.firestore.update( - { - collection: 'cities', - doc: 'SF', - subcollections: [{ collection: 'counties', doc: 'San Mateo' }], - }, - { some: 'changes' } - ); - ``` + ```js + props.firestore.update( + { + collection: 'cities', + doc: 'SF', + subcollections: [{ collection: 'counties', doc: 'San Mateo' }], + }, + { some: 'changes' }, + ); + ``` 1. How do I get auth state in redux? - 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). + 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). 1. Are there Higher Order Components for use with React? - [`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). + [`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). ## 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 +- 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!