Last active
July 18, 2018 21:45
-
-
Save markmur/327e4e6ef8ac1b22b0e001ea21142a64 to your computer and use it in GitHub Desktop.
Provide Firebase store data + subscription method to child routes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ? [] : {} | |
} | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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