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.
First, create a Firebase project using the Firebase Console. The setup is quite easy to follow with the console app. Follow these steps:
firebaseConfig
variable. You will need it later to set up communication between your app and Firebase.Now the setup is completed and you have the required config information.
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:
Provider
to the App
.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// firebaseConfig.js
2// Contains the config details of firebase, this should NOT be committed to your repo
3// if the repo is public
4// You could optionally use .env file to store these data
5
6export default {
7 apiKey: "********",
8 authDomain: "********",
9 databaseURL: "********",
10 projectId: "********",
11 storageBucket: "********",
12 messagingSenderId: "********",
13 appId: "********"
14};
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// firebase.js
2// contains the Firebase context and provider
3
4import React, { createContext, useEffect } from 'react'
5import firebaseConfig from './firebaseConfig';
6import app from 'firebase/app'
7import 'firebase/database';
8import { useDispatch } from 'react-redux';
9
10import { todoActions } from '../state/todos';
11
12// we create a React Context, for this to be accessible
13// from a component later
14const FirebaseContext = createContext(null)
15export { FirebaseContext }
16
17export default ({ children }) => {
18 let firebase = {
19 app: null,
20 database: null
21 }
22
23 const dispatch = useDispatch();
24
25 // check if firebase app has been initialized previously
26 // if not, initialize with the config we saved earlier
27 if (!app.apps.length) {
28 app.initializeApp(firebaseConfig);
29 firebase = {
30 app: app,
31 database: app.database(),
32
33 api: {
34 getTodos
35 }
36 }
37 }
38
39 // function to query Todos from the database and
40 // fire a Redux action to update the items in real-time
41 function getTodos(){
42 firebase.database.ref('todos').on('value', (snapshot) => {
43 const vals = snapshot.val();
44 let _records = [];
45 for(var key in vals){
46 _records.push({
47 ...vals[key],
48 id: key
49 });
50 }
51 // setTodos is a Redux action that would update the todo store
52 // to the _records payload
53 dispatch(setTodos(_records));
54 })
55 }
56
57 return (
58 <FirebaseContext.Provider value={firebase}>
59 {children}
60 </FirebaseContext.Provider>
61 )
62}
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// index.js
2
3import React from 'react';
4import ReactDOM from 'react-dom';
5import './index.css';
6import App from './App';
7import store from './state/store';
8import { Provider } from 'react-redux'
9import FirebaseProvider from './firebase/firebase'
10
11ReactDOM.render(
12 <React.StrictMode>
13 <Provider store={store}>
14 <FirebaseProvider>
15 <App />
16 </FirebaseProvider>
17 </Provider>
18 </React.StrictMode>,
19 document.getElementById('root')
20);
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// App.js
2
3import { FirebaseContext } from './firebase/firebase';
4
5export default function App(props) {
6 const { app, api } = useContext(FirebaseContext);
7 // you can access todos from the Redux store
8 const todos = useSelector(state => state.todo.todos);
9
10 useEffect(( ) => {
11 api.getTodos();
12 }
13 }, [])
14
15 return (
16 ...
17 )
18}
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// firebase.js
2
3// ...
4export default ({ children }) => {
5 // ...
6
7 if (!app.apps.length) {
8 app.initializeApp(firebaseConfig);
9 firebase = {
10 app: app,
11 database: app.database(),
12
13 api: {
14 getTodos,
15 addTodo
16 }
17 }
18 }
19
20 // ...
21
22 function addTodo(itemTitle){
23 firebase.database.ref('todos').push().set({
24 title: itemTitle
25 })
26 .then((doc) => {
27 // nothing to do here since you already have a
28 // connection pulling updates to Todos
29 })
30 .catch((error) => {
31 dispatch(todoActions.showError("Error adding Todo to database"));
32 console.error(error);
33 })
34 }
35}
36// ...
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.
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.