Skip to content

Instantly share code, notes, and snippets.

@adampash
Last active November 4, 2017 00:07
Show Gist options
  • Save adampash/61d8c8308d8852219e35f04a17acc7d4 to your computer and use it in GitHub Desktop.
Save adampash/61d8c8308d8852219e35f04a17acc7d4 to your computer and use it in GitHub Desktop.
HoC for connecting React components to Firestore
import React, { Component } from 'react';
// I used redux's compose b/c it was there, but you could of course compose however you wanted
import { compose } from 'redux';
// db is just the return of firebase.firestore() from an initialized Firebase
import { db } from './store';
// pipe and reduce are from https://github.com/adampash/pipe/blob/master/src/index.js
import { pipe, reduce } from './utils/pipe';
class FirestoreData extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
const { path, propKey } = this.props;
console.log(`path`, path);
if (!path) return;
const splitPath = path.split('/');
const finalPropKey = propKey || splitPath.slice(-1)[0];
this.setState({ [finalPropKey]: null });
if (splitPath.length % 2 === 0)
return this.handleCollection(path, finalPropKey);
return this.handleDoc(path, finalPropKey);
}
componentWillReceiveProps({ path, propKey }) {
if (!path) return;
const splitPath = path.split('/');
const finalPropKey = propKey || splitPath.slice(-1)[0];
this.setState({ [finalPropKey]: null });
if (splitPath.length % 2 === 0)
return this.handleCollection(path, finalPropKey);
return this.handleDoc(path, finalPropKey);
}
componentWillUnmount() {
this.unsubscribe();
}
handleWhere(accRef, query) {
return Array.isArray(query)
? accRef.where(...query)
: accRef.where(...query.split(/\s+/));
}
handleCollection(path, propKey) {
const { where, orderBy, startAt, startAfter, limit, refOnly } = this.props;
const initialRef = db.collection(path);
if (refOnly) {
this.setState({
collectionRef: initialRef,
});
return;
}
const finalRef = pipe(where)(
reduce(this.handleWhere, initialRef),
ref => (orderBy !== undefined ? ref.orderBy(...orderBy) : ref),
ref => (startAt !== undefined ? ref.startAt(startAt) : ref),
ref => (startAfter !== undefined ? ref.startAfter(startAfter) : ref),
ref => (limit !== undefined ? ref.limit(limit) : ref)
);
this.unsubscribe = finalRef.onSnapshot(collection =>
this.setState({
collectionRef: initialRef,
[propKey]: collection.docs.map(doc => ({
uid: doc.id,
...doc.data(),
docRef: doc.ref,
})),
})
);
}
handleDoc(path, propKey) {
const ref = db.doc(path);
this.unsubscribe = ref.onSnapshot(doc => {
if (doc.exists) this.setState({ [propKey]: { uid: doc.id, ...doc.data(), docRef: doc.ref });
});
}
render() {
const {
Component,
path,
propKey,
where,
limit,
orderBy,
startAt,
startAfter,
...props
} = this.props;
return <Component {...this.state} {...props} />;
}
}
const firestoreConnect = ({
path,
propKey,
where = [],
limit,
orderBy,
startAt,
startAfter,
}) => Component => props => (
<FirestoreData
Component={Component}
path={path instanceof Function ? path(props) : path}
propKey={propKey}
where={where}
limit={limit}
orderBy={orderBy}
startAt={startAt}
startAfter={startAfter}
{...props}
/>
);
const multiConnect = connections => Component => props => {
if (Array.isArray(connections)) {
const composed = compose(
...connections.map(connection => firestoreConnect(connection))
);
return composed(Component)(props);
}
return firestoreConnect(connections)(Component)(props);
};
export default multiConnect;
@adampash
Copy link
Author

adampash commented Nov 3, 2017

I sort of stole a lot of the connect API from react-redux-firebase but it didn't have Firestore support yet. Plus I didn't really want it in redux/understand the point of putting it in redux.

Usage for a collection:

const Component = ({ users }) => {
  <div>{users.map(user => <div key={user.uid}>{user.name}</div>)}</div>
}

export default firestoreConnect({
  path: '/users'
})

Usage for a document:

const Component = ({ user }) => {
  <div>{user.name}</div>
}

export default firestoreConnect({
  path: ({ userId }) => `/users/${userId}`
})

If you want to connect to multiple pieces of data, you could pass an array like:

export default firestoreConnect([
  {
    path: ({ userId }) => `/users/${userId}`
  },
  {
    path: `/somethingElse`
  },
])

You also get a docRef to do things like docRef.update(data) or collectionRef to do things like collectionRef.add(data), depending on what you asked for. I haven't thought all the edges through entirely, it's a work in progress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment