UNPKG

@taraai/read-write

Version:

Synchronous NoSQL/Firestore for React

177 lines (157 loc) 5.57 kB
import React, { Component } from 'react' import PropTypes from 'prop-types' import { isEqual, differenceWith } from 'lodash' import hoistStatics from 'hoist-non-react-statics' import { watchEvents, unWatchEvents } from './actions/query' import { getEventsFromInput, createCallable, wrapDisplayName } from './utils' import ReactReduxFirebaseContext from './ReactReduxFirebaseContext' /** * @augments React.Component * @description React Higher Order Component that automatically listens/unListens to * Firebase Real Time Database on mount/unmount of the component. This uses * React's Component Lifecycle hooks. * @param {Array|Function} queriesConfig - Array of objects or strings for paths to sync * from Firebase. Can also be a function that returns the array. The function * is passed the current props and the firebase object. * @returns {Function} - that accepts a component to wrap and returns the wrapped component * @see https://react-redux-firebase.com/docs/api/firebaseConnect.html * @example <caption>Basic</caption> * // props.firebase set on App component as firebase object with helpers * import { firebaseConnect } from 'react-redux-firebase' * export default firebaseConnect()(App) * @example <caption>Ordered Data</caption> * import React from 'react' * import { compose } from 'redux' * import { connect } from 'react-redux' * import { firebaseConnect } from 'react-redux-firebase' * * const enhance = compose( * firebaseConnect([ * 'todos' // sync /todos from firebase into redux * ]), * connect((state) => ({ * todos: state.firebase.ordered.todos * })) * ) * * function Todos({ todos }) { * return ( * <div> * {JSON.stringify(todos, null, 2)} * </div> * ) * } * * export default enhance(Todos) * @example <caption>Data that depends on props</caption> * import React from 'react' * import { compose } from 'redux' * import { connect } from 'react-redux' * import { get } from 'lodash' * import { firebaseConnect } from 'react-redux-firebase' * * const enhance = compose( * firebaseConnect((props) => ([ * `posts/${props.postId}` // sync /posts/postId from firebase into redux * ])), * connect((state, props) => ({ * post: get(state.firebase.data, `posts.${props.postId}`), * })) * ) * * function Post({ post }) { * return ( * <div> * {JSON.stringify(post, null, 2)} * </div> * ) * } * * export default enhance(Post) */ export default function firebaseConnect(queriesConfig = []) { return (WrappedComponent) => { class FirebaseConnectWrapped extends Component { static displayName = wrapDisplayName( WrappedComponent, 'FirebaseConnectWrapped' ) static wrappedComponent = WrappedComponent firebaseEvents = [] firebase = null prevData = null componentDidMount() { const { firebase, dispatch } = this.props // Allow function to be passed const inputAsFunc = createCallable(queriesConfig) this.prevData = inputAsFunc(this.props, this.props) const { ref, helpers, storage, database, auth } = firebase this.firebase = { ref, storage, database, auth, ...helpers } this._firebaseEvents = getEventsFromInput(this.prevData) watchEvents(firebase, dispatch, this._firebaseEvents) } componentWillUnmount() { const { firebase, dispatch } = this.props unWatchEvents(firebase, dispatch, this._firebaseEvents) } /* eslint-disable camelcase */ UNSAFE_componentWillReceiveProps(np) { /* eslint-enable camelcase */ const { firebase, dispatch } = this.props const inputAsFunc = createCallable(queriesConfig) const data = inputAsFunc(np, this.store) // Handle a data parameter having changed if (!isEqual(data, this.prevData)) { const itemsToSubscribe = differenceWith(data, this.prevData, isEqual) const itemsToUnsubscribe = differenceWith( this.prevData, data, isEqual ) this.prevData = data // UnWatch all current events unWatchEvents( firebase, dispatch, getEventsFromInput(itemsToUnsubscribe) ) // Get watch events from new data this._firebaseEvents = getEventsFromInput(data) // Watch new events watchEvents(firebase, dispatch, getEventsFromInput(itemsToSubscribe)) } } render() { return <WrappedComponent {...this.props} /> } } FirebaseConnectWrapped.propTypes = { dispatch: PropTypes.func.isRequired, firebase: PropTypes.object.isRequired } /** * Render component wrapped in context * @param {object} props - Component props * @returns {React.Component} Component wrapped in context */ function FirebaseConnectWithContext(props) { return ( <ReactReduxFirebaseContext.Consumer> {(_internalFirebase) => ( <FirebaseConnectWrapped {...props} dispatch={_internalFirebase.dispatch} firebase={_internalFirebase} /> )} </ReactReduxFirebaseContext.Consumer> ) } FirebaseConnectWithContext.displayName = wrapDisplayName( WrappedComponent, 'FirebaseConnect' ) FirebaseConnectWithContext.wrappedComponent = WrappedComponent return hoistStatics(FirebaseConnectWithContext, WrappedComponent) } }