Search:   Dictionary All Posts
Store Your Knowledge at the Brain Bank!

Firebase - Firestore w/React - Database Guide

By Jamie in Lessons / Programming - React  1.25.19  (Source)
Summary - Setup a Firebase project. Start a Firestore database. Sync firestore and firebase reducers with the rootReducer. Add data via actions and update the state. Retrieve and display data on the frontend.
Create Project
Create a new project in Firebase

Config File
Create a config/fbConfig.js file and store config data from Firebase inside

NPM Installs
npm install firebase
npm install react-redux-firebase
npm install redux-firestore

Index.js Imports
- import two Fire related functions to be passed via middleware. 
import { getFirebase } from 'react-redux-firebase'
import  { getFirestore } from 'react-firestore'

- then we pass these functions as an object w/ properties into the thunk.withExtraArgument function 

createStore Code
const store = createStore(rootReducer,
    applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore })
)
- note - this is .withExtraArgument, not .withExtraArguments, because it only takes one extra argument, an object, which can have unlimited properties of course

Redux Store Enhancers - compose()
- applying middleware to a store is considered a 'store enhancer', where it takes in the store and returns the store but with added benefits.
- the first enhancer is middleware called thunk. thunk passes dispatch and getState functions as parameters to action functions in order to allow async actions. thunk has a function called .withExtraArguments, which allows it to pass on object with properties to be used as additional arguments, in this case we will pass the getFirebase and getFirestore arguments
- in order for the action to use those two arguments properly, they must be able to connect to Firebase and the Firestore, which they must do by accessing the /config/fbConfig.js file
- in order to do this we must use additional 'enhancers' for the store
- in order to use multiple enhancers in redux we must use the function 'compose'
- thus, we must import 'compose' from redux as such...
import { createStore, applyMiddleware, compose } from 'redux'
- in order to link this all together we must import 2 additional store enhancers which we will use in the compose method. These enhancers will be in addition to the middleware. So the store will be 'enhanced' by the middleware, which in this case means it will get extra arguments. Then we must 'enhance' the store with a connection to Firebase and another connection to the Firestore.
- First we must add imports, then we must use compose to send to our store...

import { reactReduxFirebase, getFirebase } from 'react-redux-firebase'
import  { reduxFirestore, getFirestore } from 'react-firestore'

import fbConfig from './config/fbConfig'

const store = createStore(rootReducer,
    compose(
        applyMiddleware(thunk.withExtraArgument({ getFirebase, getFirestore })),
        reduxFirestore(fbConfig),
        reactReduxFirebase(fbConfig)
    )
)

NOTE - in versions >= 3 of react-reduct-firebase, these connection methods are deprecated. See the migration guide to connect properly in those versions...

firebaseAuthIsReady
Finally, when a site depends on firebase login data - say, to display certain data if a user is logged in, then we must put the ReactDOM.render() method inside an async function, so that it doesn't render until after firebase checks if the user is logged in. This will help avoid the user seeing wrong information on the first render, then see a flash as the DOM re-renders with the logged in data.

store.firebaseAuthIsReady.then(() => {
    ReactDOM.render( // stuff in here // )
})

Dispatch an Action to the Store
You do the normal mapDispatchToProps using connect(null, mapDispatchToProps)(Component)

const mapDispatchToProps = (dispatch) => {
    return {
        createProject: (project) => dispatch(createProject(project))
    }
}

export default connect(null, mapDispatchToProps)(Component)



Syncing the Firestore to our State using the Firestore Reducer
Simply add a reducer property to the rootReducer which will be 'firestore' as so...

import { combineReducers } from 'redux'
import { firestoreReducer } from 'redux-firestore'

const rootReducer = combineReducers({
    auth: authReducer,
    project: projectReducer,
    firestore: firestoreReducer
})

export default rootReducer

Using Firestore in the Actions to Set State
Now, when the action (createProject(project)) is dispatched, we go to the 'createProject' action, which gets the project passed in and we see that it returns a function instead of an object.

thunk recognizes a function instead of an object is being returned so it pauses the dispatch function from redux and allows us to run some async code in the returned function.

using the thunk middleware setup on the index.js page, we can use thunk's dispatch and getState functions passed in as arguments of the returned function, as well as getFirebase and getFirestore as extra arguments.

In the returned function we can now call those two firebase functions, which will connect to the firebase firestore (our current database in firestore)

export const createProject = (project) => {
    return(dispatch, getState, { getFirebase, getFirestore }) => {
        const firestore = getFirestore()
        firestore.collection('project').add({
            ...project,
            authFirstName: 'Jamie',
            authLastName: 'Miller',
            created: new Date()
        })
            .then(() => dispatch({ type: CREATE_PROJECT, project })
            .catch(err => dispatch({ type: CREATE_PROJECT_ERROR, err })
        })
    }
}

Getting Data from the Firestore - firestoreConnect
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'

const Component = ({ project }) => {
    
}

const mapStateToProps = (state, ownProps) => {
    const id = ownProps.match.params.id
    const projects = state.firestore.data.projects
    const project = projects ? projects[id] : null
    return { project }
}

export default compose(
    connect(mapStateToProps)
    firestoreConnect([
        { collection: 'projects' }
    ])
)(Component)