Firebase with Redux

Preface

There have been many questions coming up in the community about combining Firebase with Redux. In this article we will explore the reason for combining the two and go through a simple example.

First, lets start by addressing one of the most common questions within the community:

Since Firebase is already a state management tool, why would I need to combine it with Redux? Why not simply listen for data changes in each component?

Understandable question. Quick answer: Though Firebase stores state, that doesn’t mean that all of your application state should be there. I like to think of it as two different states, ‘local state’ or ‘internal state’ and ‘persisted state’. ‘Local State’ refers to state that is specific to a single client, like button states and current path, whereas ‘persisted state’ refers to state that will persist to all clients, such as messages and user profiles.

This simple answer is often followed by:

Ok, sounds simple enough… Two states… Now how do I make those states available to my components?

Redux passes state to React components by using something called a Higher Order Component (HOC for short). Without getting too technical, you can think of HOCs as a wrapper that passes data/functions you will need to your components.

Lets review this concept more through reviewing the todomvc redux example:

import React, { PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as TodoActions from '../actions'
const App = ({todos, actions}) => (
<div>
{todos.map(todo => (
<p>{todo.text}</p>
)
}
</div>
)
App.propTypes = {
todos: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
todos: state.todos
})
export default connect(mapStateToProps)(App)

connect is a HOC that passes redux state into props of your components. In this example, it is todos is passed from redux state into the the todos prop.

Firebase Connect

Ok, I understand connecting to Redux state with the connect function, now where does Firebase fit into all of this?

In the same way that react-redux provides the connect HOC, there are HOC libraries for connecting to Firebase. My favorite one (biased since I made it) is react-redux-firebase, so the following examples will use that.

Full Example

To illustrate implementation, we run through an example that starts with the output of create-react-app (install it if you don’t already have it). There is also a completed version of this example, and multiple other examples within the react-redux-firebase repo.

  1. Create project named reduxFirebase by running create-react-app reduxFirebase
  2. Enter the project folder ( cd reduxFirebase on mac)
  3. Install redux, redux-react, and react-redux-firebase by running npm i --save redux react-redux react-redux-firebase
  4. Create src/reducers.js and make it look like this:
import { combineReducers } from 'redux'
import { firebaseStateReducer as firebase } from 'react-redux-firebase'
const rootReducer = combineReducers({
firebase
})
export default rootReducer

5. Create src/store.js and make it look like this:

import { createStore, compose } from 'redux'
import rootReducer from './reducers'
import { reduxFirebase } from 'react-redux-firebase'
// Replace with your Firebase config
const fbConfig = {
apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots',
authDomain: 'redux-firebasev3.firebaseapp.com',
databaseURL: 'https://redux-firebasev3.firebaseio.com'
}
export default function configureStore (initialState, history) {
const createStoreWithMiddleware = compose(
reduxFirebase(fbConfig, { userProfile: 'users' }),
// Redux Devtools
typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
)(createStore)
const store = createStoreWithMiddleware(rootReducer)
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('./reducer', () => {
const nextRootReducer = require('./reducer')
store.replaceReducer(nextRootReducer)
})
}
  return store
}

6. Create src/TodoItem.js and make it look like this:

import React, { PropTypes, Component } from 'react'
import { firebase } from 'react-redux-firebase'
import './Todo.css'
class TodoItem extends Component {
static propTypes = {
todo: PropTypes.object,
id: PropTypes.string
}
render(){
const {firebase, todo, id} = this.props
const toggleDone = () => {
firebase.set(`/todos/${id}/done`, !todo.done)
}
    const deleteTodo = (event) => {
firebase.remove(`/todos/${id}`)
}
    return (
<li>
<input
className="Todo-Input"
type="checkbox"
checked={todo.done}
onChange={toggleDone}
/>
{todo.text}
<button onClick={deleteTodo}>
Delete
</button>
</li>
)
}
}
//HOC Adds this.props.firebase
export default firebase()(TodoItem)

7. Finally, bring it all together in src/App.js:

import React, { Component, PropTypes } from 'react'
import TodoItem from './TodoItem'
// redux/firebase
import { connect } from 'react-redux'
import { firebase, helpers } from 'react-redux-firebase'
const { isLoaded, isEmpty, pathToJS, dataToJS } = helpers
class App extends Component {
static propTypes = {
todos: PropTypes.object,
firebase: PropTypes.shape({
push: PropTypes.func.isRequired
})
}
render () {
const { firebase, todos } = this.props
    const handleAdd = () => {
const { newTodo } = this.refs
firebase.push('/todos', { text: newTodo.value, done: false })
newTodo.value = ''
}
    const todosList = (!isLoaded(todos))
? 'Loading'
: (isEmpty(todos))
? 'Todo list is empty'
: Object.keys(todos).map((key) => (
<TodoItem key={key} id={key} todo={todos[key]} />
))
return (
<div>
<h4>
Loaded From
<span>
<a href='https://redux-firebasev3.firebaseio.com/'>
redux-firebasev3.firebaseio.com
</a>
</span>
</h4>
<h4>Todos List</h4>
{todosList}
<h4>New Todo</h4>
<input type='text' ref='newTodo' />
<button onClick={handleAdd}>Add</button>
</div>
)
}
}
const fbWrappedComponent = firebase([
'/todos'
])(App)
export default connect(
({firebase}) => ({
todos: dataToJS(firebase, 'todos'),
profile: pathToJS(firebase, 'profile')
})
)(fbWrappedComponent)

Generator

If you don’t want to go through these steps every time you want to start a project with this configuration, you can use the yeoman generator: generator-react-firebase.