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.

Create project named reduxFirebase by running create-react-app reduxFirebase Enter the project folder ( cd reduxFirebase on mac) Install redux, redux-react, and react-redux-firebase by running npm i --save redux react-redux react-redux-firebase 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'

const fbConfig = {

apiKey: 'AIzaSyCTUERDM-Pchn_UDTsfhVPiwM4TtNIxots',

authDomain: 'redux-firebasev3.firebaseapp.com',

databaseURL: '

} // Replace with your Firebase configconst 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 = ''

}

? '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='

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) const todosList = (!isLoaded(todos))? 'Loading': (isEmpty(todos))? 'Todo list is empty': Object.keys(todos).map((key) => ( ))return ( Loaded From https://redux-firebasev3.firebaseio.com/' redux-firebasev3.firebaseio.com Todos List {todosList} New Todo Add 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.