Author avatar

Manujith Pallewatte

Using Firebase with React and Redux

Manujith Pallewatte

  • Apr 9, 2020
  • 10 Min read
  • 9,499 Views
  • Apr 9, 2020
  • 10 Min read
  • 9,499 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

Firebase is an extremely useful platform for rapidly creating serverless applications. Apart from that, Firebase is also used alongside end-to-end systems to solve several production-grade issues, such as config management and push notifications. Firebase Realtime Database is one of the most popular services offered by the platform. It gives a quick database and a backend for web apps that need to be built and scaled fast.

Connecting a Firebase database to a React app is, unfortunately, not straightforward. There are some confusing things about the way data sync in Firebase integrates with how React handles state. To get the most out of Firebase, it's best to integrate it in a manner that complements the uni-directional data flow pattern of React.

In this guide, we explore one such method implemented using React Context. The example we use is the popular React Todo app, with Redux used for app state management. You should first go through this example in the Redux Docs if you're not familiar with it.

Create a Firebase Project

First, create a Firebase project using the Firebase Console. The setup is quite easy to follow with the console app. Follow these steps:

  1. Goto Add Project from the console.
  2. Enter a project name and continue.
  3. You can opt in to include Google Analytics in the project. Opt out for now.
  4. Once the setup is done, you will be redirected to the project dashboard. Go to Project Settings.
  5. Under Your Apps, select the Web icon.
  6. Enter a name for the app and click Register App.
  7. Copy the config details that come under the firebaseConfig variable. You will need it later to set up communication between your app and Firebase.
  8. Press Continue to Console to complete web app registration.

Now the setup is completed and you have the required config information.

Connect React Firebase

There are several correct ways to connect Firebase with your React app. In this guide, you will explore the React Context-based method. Firebase offers a range of services in deploying serverless apps, including the real-time database, the Cloud Firestore, Firebase Storage (for storing files), etc. You will be using only the real-time database for now. But the connectivity method you implement will also have room to use any of the above services at a later stage.

Note:
One important fact to keep in mind regarding the Firebase Realtime Database is that, as the name suggests, it pushes data updates to the app in real time. This means that in contrast to conventional data sources where you pull the information when required, Firebase will continuously sync your data updates once you start listening to a data query. For example, when you signal that you want to listen to all Todo items in the database, you will be continuously updated on any updates to the Todo collection in the database. This is facilitated using WebSockets. (If you need an intro to WebSockets, this guide gives you an in-depth look, but it's not necessary in order to continue with the current guide.)

The React Context you implement for Firebase connectivity will have the following functions:

  1. It will initiate a connection with Firebase at the app init.
  2. It will make sure that only one connection per app instance will exist with Firebase.
  3. It will be available to be used in any component once injected as a Provider to the App.
  4. It will interface all read/write operations to the real-time database.
  5. It will integrate with Redux directly.

Add Firebase to your React project using npm install --save firebase or yarn add firebase.

To keep things clean, create a new directory firebase inside the src directory and create the following files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// firebaseConfig.js
// Contains the config details of firebase, this should NOT be committed to your repo
// if the repo is public
// You could optionally use .env file to store these data

export default {
    apiKey: "********",
    authDomain: "********",
    databaseURL: "********",
    projectId: "********",
    storageBucket: "********",
    messagingSenderId: "********",
    appId: "********"
};
javascript

All the above details are available in the config you copied when setting up the Firebase project. If you didn't store it before, you can find it again by going to Project Settings in the project dashboard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// firebase.js
// contains the Firebase context and provider

import React, { createContext, useEffect } from 'react'
import firebaseConfig from './firebaseConfig';
import app from 'firebase/app'
import 'firebase/database';
import { useDispatch } from 'react-redux';

import { todoActions } from '../state/todos';

// we create a React Context, for this to be accessible
// from a component later
const FirebaseContext = createContext(null)
export { FirebaseContext }

export default ({ children }) => {
    let firebase = {
        app: null,
        database: null
    }

    const dispatch = useDispatch();

    // check if firebase app has been initialized previously
    // if not, initialize with the config we saved earlier
    if (!app.apps.length) {
        app.initializeApp(firebaseConfig);
        firebase = {
            app: app,
            database: app.database(),

            api: {
                getTodos
            }
        }
    }

    // function to query Todos from the database and
    // fire a Redux action to update the items in real-time
    function getTodos(){
        firebase.database.ref('todos').on('value', (snapshot) => {
            const vals = snapshot.val();
            let _records = [];
            for(var key in vals){
                _records.push({
                    ...vals[key],
                    id: key
                });
            }
            // setTodos is a Redux action that would update the todo store
            // to the _records payload
            dispatch(setTodos(_records));
        })
    }

    return (
        <FirebaseContext.Provider value={firebase}>
            {children}
        </FirebaseContext.Provider>
    )
}
javascript

Next, add the Firebase Provider to your app. Make sure that when you do, it comes after the Redux Provider. If you add it before, it won't have access to the useDispatch hook. In this example, you will define the provider in index.js and attach all Provider on top of the App component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import store from './state/store';
import { Provider } from 'react-redux'
import FirebaseProvider from './firebase/firebase'

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        <FirebaseProvider>
            <App />
        </FirebaseProvider>
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);
javascript

Now your connection to Firebase is complete. At your app init, call the getTodos function once and Firebase will create a sync between the real-time database and the Redux store. Every new update to the Todo collection will be reflected in the Redux store.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// App.js

import { FirebaseContext } from './firebase/firebase';

export default function App(props) {
    const { app, api } = useContext(FirebaseContext);
    // you can access todos from the Redux store
    const todos = useSelector(state => state.todo.todos);

    useEffect(( ) => {
        api.getTodos();
        }
    }, [])

    return (
        ...
    )
}
javascript

Edit Real-time Database with React

Having the above setup done, writing and updating data to the Firebase database is a breeze. You could either do a database update straight from each component or interface it through the Firebase context in a more controlled manner. The latter is recommended for a complex project so that all the Firebase communication resides in one place rather than scattered in multiple components.

First add a method to create a Todo in the Firebase Context.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// firebase.js

// ...
export default ({ children }) => {
    // ...

    if (!app.apps.length) {
        app.initializeApp(firebaseConfig);
        firebase = {
            app: app,
            database: app.database(),

            api: {
                getTodos,
                addTodo
            }
        }
    }

    // ...

    function addTodo(itemTitle){
        firebase.database.ref('todos').push().set({
            title: itemTitle
        })
        .then((doc) => {
            // nothing to do here since you already have a 
            // connection pulling updates to Todos
        })
        .catch((error) => {
            dispatch(todoActions.showError("Error adding Todo to database"));
			console.error(error);
		})
    }
}
// ...
javascript

Then simply call the addTodo function by calling api.addTodo("some title") from the component where you create Todos. We have used the push function of Firebase for adding a new Todo since it is the recommended way for creating items in a list. You can read more about it in the Firebase Documentation for Saving Data.

Conclusion

Firebase is a popular platform for quickly building serverless apps. There are many ways to connect a React and Redux app to your Firebase project. In this guide, we explored a method that would scale well in a production-grade app. The ability for the Firebase Realtime Database to sync with the Redux store without causing unexpected issues is a key benefit of this method. Finally, we discussed how to use the same interface to simplify adding and updating records to the Firebase database.

51