firebase-connect

Get data from firebase and store it into props

Usage no npm install needed!

<script type="module">
  import firebaseConnect from 'https://cdn.skypack.dev/firebase-connect';
</script>

README

firebase-connect

HOC for connecting to firebase realtime database. Based on react-redux-firebase to improve connection to firebase.

DEMO

SOURCE OF DEMO APP

Npm Version Month Downloads Npm Licence

Instalation

npm install --save firebase-connect

How firebase-connect improves react-redux-firebase

  • It stores received data for each path from realtime database as long as the component with this HOC is mounted
  • It merges data of any number of listeners that you'll define
  • It creates property ${propName} with received data and boolean property is${PropName}Loaded that helps to define was the data loaded or not
  • You can set default value, that can be taken from your store or defined manually
  • You can modify received data

Usage

import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import connect from 'react-redux'
import { firebaseConnect } from 'firebase-connect' // import firestoreConnect if you using firestore


@connect({
  activeChat: (state) => state.activeChat.name,
  endAt: (state) => state.activeChat.endAt,
})
// set same props for @firestoreConnect() if you using firestore
@firebaseConnect((props) => ({
  chatMessages: {
    // path to listen in firebase realtime database
    path: `chatMessages/${props.activeChat}`,
    listeners: Boolean(props.endAt) ? [
      // get 25 messages where property 'added' >= props.endAt
      // props.endAt is used to load older messages if user scroll up to load more
      [
        'orderByChild=added',
        `endAt=${props.endAt}`,
        'limitToLast=25',
      ],
      // listen to newest messages
      [
        'orderByChild=added',
        'limitToLast=5',
      ],
    ] : [
      // load last 25 messages if there is no props.endAt
      [
        'orderByChild=added',
        'limitToLast=25',
      ]
    ],
    // Get default value from preloaded store (on SSR) or set it manually
    // It also can be used as a function: (state) => state.preload.chats || [],
    defaultValue: `preload.chatMessages.${props.activeChat}`,
    // Modify output result
    modifyResult: (chatMessages = {}) =>
      Object.keys(chatMessages)
        .filter((key) => chatMessages[key])
        .map((key) => ({ id: key, ...chatMessages[key] }))
        // Object from database is not sorted, so you need to sort it manually
        .sort((a, b) => b.added - a.added),
  },
  // Load some other data if needed
  chats: {
    path: 'chats',
    listeners: [
      [ 'orderByChild=added' ],
    ],
    defaultValue: 'preload.chats',
    modifyResult: (chats = {}) => Object.keys(chats)
      .map((key) => ({ id: key, ...chats[key] }))
      .map((chat) => ({
        ...chat,
        isActive: props.activeChat === chat.title,
        users: Object.keys(chat.users).map((key) => chat.users[key]),
      })),
  },
  // pass connect as second parameter to rerender component when data is received
}), connect)
export default class Chat extends PureComponent {

  static propTypes = {
    chats: PropTypes.array,
    chatMessages: PropTypes.array,
    // The properties below is added automatically and helps to understend
    // is data loading or already was loaded
    isChatsLoaded: PropTypes.bool,
    isChatMessagesLoaded: PropTypes.bool,
  }

  render() {
    const { chats, chatMessages, isChatsLoaded, isChatMessagesLoaded } = this.props
    
    const isChatsAndMessagesLoaded = isChatsLoaded && isChatMessagesLoaded

    return isChatsAndMessagesLoaded ? (
      <div>
        <div>
          <div>Chats:</div>
          <div>
            {
              chats.map(({ id, title }) => (
                <div key={id}>
                  Chat title: {title}
                </div>
              ))
            }
          </div>
          <div>Active chat messages:</div>
          <div>
            {
              chatMessages.map(({ id, author, message }) => (
                <div key={id}>
                  <div>{author}:</div>
                  <div>{message}</div>
                </div>
              ))
            }
          </div>
        </div>
      </div>
    ) : (
      <div>Data is loading...</div>
    )
  }
}

Same as using react-redux-firebase you need to include reduxFirebase (store enhancer) and firebaseReducer (reducer) while creating your redux store:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers, compose } from 'redux'
import {
  reduxFirebase,
  firebaseReducer,
  reduxFirestore, // <- needed if using firestore
  firestoreReducer, // <- needed if using firestore
} from 'firebase-connect'
import firebase from 'firebase'
// import 'firebase/firestore' // <- needed if using firestore
// import 'firebase/functions' // <- needed if using httpsCallable
// Get firebase config (includes apiKey, authDomain, databaseURL, projectId, storageBucket, messagingSenderId)
import firebaseConfig from 'firebase-config.json'

// Initialize firebase instance
firebase.initializeApp(firebaseConfig)

// Initialize other services on firebase instance
// firebase.firestore().settings({ timestampsInSnapshots: true }) // <- needed if using firestore
// firebase.functions() // <- needed if using httpsCallable

// Add reduxFirebase enhancer when making store creator
const createStoreWithFirebase = compose(
  reduxFirebase(firebase, {
    userProfile: 'users',
    // useFirestoreForProfile: true // Firestore for Profile instead of Realtime DB
  }),
  // reduxFirestore(firebase) // <- needed if using firestore
)(createStore)

// Add firebase to reducers
const rootReducer = combineReducers({
  firebase: firebaseReducer,
  // firestore: firestoreReducer // <- needed if using firestore
})

// Create store with reducers and initial state
const initialState = {}
const store = createStoreWithFirebase(rootReducer, initialState)

// Setup react-redux so that connect HOC can be used
const App = () => (
  <Provider store={store}>
    <Todos />
  </Provider>
);

render(<App/>, document.getElementById('root'));

The Firebase instance can be grabbed using getFirebase function (for example in actions or somewhere you can't apply the HOC)

import { getFirebase } from 'firebase-connect'


const push = ({ message }) =>
  getFirebase().push('chatMessages/activeChat', {
    added: Date.now(),
    message,
  })
  
  
export default {
  push,
}