Skip to content

Instantly share code, notes, and snippets.

@markmur
Last active July 18, 2018 21:45
Show Gist options
  • Save markmur/327e4e6ef8ac1b22b0e001ea21142a64 to your computer and use it in GitHub Desktop.
Save markmur/327e4e6ef8ac1b22b0e001ea21142a64 to your computer and use it in GitHub Desktop.
Provide Firebase store data + subscription method to child routes
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import FirebaseProvider, { Consumer as FirebaseConsumer } from './FirebaseProvider'
class Container extends Component {
render() {
return (
<Router>
<FirebaseProvider>
<FirebaseConsumer>
{store =>
<Switch>
<Route
path="/some/route"
render={props => <SomeRoute {...props} store={store} />}
/>
</Switch>
}
</FirebaseConsumer>
</FirebaseProvider>
</Router>
)
}
}
export default Container
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { queries, setDefaultState, DOC, COLLECTION } from './queries'
const defaultState = {}
const { Provider, Consumer } = React.createContext(defaultState)
class FirebaseProvider extends Component {
static propTypes = {
children: PropTypes.any.isRequired
}
static defaultProps = {
user: {}
}
constructor() {
super()
const defaultStoreState = Object.keys(queries).reduce(
(state, key) => ({
...state,
...setDefaultState(key, queries[key].type)
}),
{}
)
const actions = {
subscribe: this.subscribe,
unsubscribe: this.unsubscribe,
setGlobalLoadingState: this.setGlobalLoadingState,
documentExists: this.documentExists
}
this.state = {
// Global loading state
loading: false,
// Default state for all queries { loading, hasData, data }
...defaultStoreState,
// Actions
...actions
}
this.subscriptions = {}
}
componentWillUnmount() {
// Unsubscribe from all subscriptions
Object.values(this.subscriptions).map(x => typeof x === 'function' && x())
}
subscribe = (query, ...args) => {
if (!(query in queries) || typeof query !== 'string') {
throw new Error('Query does not exist')
}
const { type } = queries[query]
const lastArg = args.slice().pop()
const callback = typeof lastArg === 'function' ? lastArg : () => {}
this.setLoadingState(query, true)
this.subscriptions[query] = queries[query]
.get(this.props.user.uid, ...args)
.onSnapshot(snapshot => {
if (type === DOC) {
const { exists } = snapshot
callback(exists)
if (!exists) return
}
return type === COLLECTION
? this.setCollectionToState(query, snapshot)
: this.setDocumentToState(query, snapshot)
})
return this.subscriptions[query]
}
unsubscribe = query => {
this.setState(setDefaultState(query, queries[query].type))
return query in this.subscriptions && typeof this.subscriptions[query]
? this.subscriptions[query]()
: null
}
setLoadingState = (key, loading, global = true) =>
this.setState(state => ({
loading: global ? loading : false,
[key]: {
...state[key],
loading
}
}))
setGlobalLoadingState = loading =>
this.setState({
loading
})
setCollectionToState = (key, snapshot) =>
this.setState({
loading: false,
[key]: {
loading: false,
hasData: snapshot.docs.length > 0,
data: snapshot.docs.map(x => ({
...x.data(),
id: x.id
}))
}
})
setDocumentToState = (key, snapshot) =>
this.setState({
loading: false,
[key]: {
loading: false,
hasData: snapshot.exists,
data: snapshot.data() || {}
}
})
render() {
return <Provider value={this.state}>{this.props.children}</Provider>
}
}
export { Consumer }
export default FirebaseProvider
import firebase from 'firebase/app'
import 'firebase/firestore'
const config = {
apiKey: '',
authDomain: '',
databaseURL: '',
projectId: '',
storageBucket: '',
messagingSenderId: ''
}
let app
if (firebase.apps.length <= 0) {
app = firebase.initializeApp(config)
}
const db = firebase.firestore(app)
db.settings({ timestampsInSnapshots: true })
export { db }
export const COLLECTION = 'collection'
export const DOC = 'doc'
export const queries = {
someCollection: {
get: uid => db.collection(someCollection).where(`members.${uid}`, '==', true),
type: COLLECTION
},
someDocument: {
get: (uid, id) => db.collection(someCollection).doc(id),
type: DOC
}
}
export const setDefaultState = (key, type) => ({
[key]: {
loading: false,
hasData: false,
data: type === COLLECTION ? [] : {}
}
})
import React, { Component } from 'react'
class SomePage extends Component {
componentDidMount() {
const { store, match, history } = this.props
this.unsubscribe = store.subscribe('someDocument', match.params.id, exists => {
if (!exists) history.replace('/')
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const { store } = this.props
const doc = store.someDocument.data
return <div />
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment