The release of version 9 of the Firebase Web SDK has introduced breaking changes in methods for managing users and querying databases. Code written in Firebase v8.x will throw errors when used in v9.x, which calls for refactoring.
In this article, we’ll learn how to refactor a React app that uses the Firebase Web SDK v8.x to v9.x., which is also called the modular Web SDK. For our example, we’ll use an Amazon clone built with v8.x and refactor it to v9.x. Let’s get started!
To follow along with this tutorial, you should be familiar with React and Firebase v8.x. You should also have Node.js installed on your machine.
The new web SDK moves away from the namespace approach that was used in version 8. Instead, it adopts a modular format optimized for eliminating unused code, for example, tree shaking, resulting in a significant reduction in the JavaScript bundle size.
The transition to a modular approach has introduced breaking changes, making the new library backward incompatible and causing the code used in v8.x to throw errors in the new Firebase v9.x SDK.
The following code shows some of the breaking changes introduced in the new library:
// VERSION 8 import firebase from 'firebase/app'; import 'firebase/auth'; firebase.initializeApp(); const auth = firebase.auth(); auth.onAuthStateChanged(user => { // Check for user status }); // VERSION 9 EQUIVALENT import { initializeApp } from 'firebase/app'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const firebaseApp = initializeApp(); const auth = getAuth(firebaseApp); onAuthStateChanged(auth, user => { // Check for user status });
Both code samples above monitor a user state. Although both are similar in the number of lines of code used, in v9.x, instead of importing the firebase
namespace or the firebase/auth
side effect, which augments authentication service to the firebase
namespace, we are importing and using individual functions.
These changes take advantage of the code elimination features of modern JavaScript tools like Webpack and Rollup.
For example, the v8.x code above includes the following code snippet:
auth.onAuthStateChanged(user => { // Check for user status });
auth
is a namespace and a service that contains the onAuthStateChanged
method. The namespace also contains methods like signInWithEmailAndPassword
, createUserWithEmailAndPassword
, and signOut
, which are not being used by the code. When we bundle our entire code, these unused methods will also be included in the bundle, resulting in a relative size increase.
Though bundlers like Webpack and Rollup can be used to eliminate unused code, due to the namespace approach, they will have no effect. Solving this issue is one of the primary goals of remodeling the API surface to take a modular shape. To learn more about the reasons behind the changes in the new library, check out the official Firebase blog.
The new SDK also includes a compatibility library with a familiar API surface, which is fully compatible with v8.x. The compatibility library allows us to use both old and new APIs in the same codebase, enabling us to progressively refactor our app without breaking it. We can use the compatibility library by making a few tweaks to the import paths as follows:
import firebase from 'firebase/compat/app'; import 'firebase/compat/auth'; import 'firebase/compat/firestore';
We’ll take advantage of the library when we refactor our Amazon clone app.
In short, the Firebase Web SDK v9.x offers reduced size and increased performance overall. By taking advantage of code elimination features through JavaScript tools like Webpack and Rollup, the new web SDK offers a faster web experience. With the new modular shape, the new SDK is said to be about 80 percent smaller than its predecessors, according to the official Firebase Twitter account.
Now that we’re familiar with the new SDK, let’s learn how to refactor our v8.x app. The Amazon clone app that we’ll use in this section is an ecommerce app built using Firebase and Strapi.
In our app, we used Firebase to add features like managing user identity with Firebase authentication and storing products purchased by authenticated users with Cloud Firestore. We used Strapi to handle payments for products bought on the app. Finally, we created an API with Express.js that responds with the Strapi client secret of a customer who is about to purchase a product with Firebase Cloud Functions.
You can access a deployed version of the site, which looks like the image below:
Feel free to play around with the app to better understand what we’re working on in this article.
Before we begin coding, first, let’s clone the repo from GitHub and install the necessary npm packages. Open your terminal and navigate to the folder you would like to store the React app in. Add the following commands:
$ git clone https://github.com/Tammibriggs/Amazon-clone-FirebaseV8.git $ cd Amazon-clone-FirebaseV8
Now that we’ve successfully cloned the repo, we need to change the Firebase version in the package.json
file to v9.x before we install the packages.
In the root directory, open the package.json
file and replace "firebase": "8.10.0"
in the dependencies object with "firebase": "9.2.0"
. Now, let’s install our app’s dependencies by running the following command in the terminal:
$ npm install $ cd functions $ npm install
Although we’ve set up and installed all of our app’s dependencies, if we try running the app with npm start
, it will throw errors. To avoid this, we need to fix our app’s breaking changes, which we’ll do shortly.
The structure for the src
directory of our app is as follows, but we’ve removed all the style files to make it look shorter:
src ┣ Checkout ┃ ┣ Checkout.js ┃ ┣ CheckoutProduct.js ┃ ┗ Subtotal.js ┣ Header ┃ ┗ Header.js ┣ Home ┃ ┣ Home.js ┃ ┗ Product.js ┣ Login ┃ ┗ Login.js ┣ Orders ┃ ┣ Order.js ┃ ┗ Orders.js ┣ Payment ┃ ┣ axios.js ┃ ┗ Payment.js ┣ App.js ┣ firebase.js ┣ index.js ┣ reducer.js ┣ reportWebVitals.js ┗ StateProvider.js
We’ll only be working with the files that use Firebase services, firebase
, App.js
, Header.js
, Login.js
, Payment.js
, and Orders.js
,
Let’s update to the v9.x compat library, helping us to progressively migrate to a modular approach until we no longer have any need for the compat library.
The upgrade process follows a repeating pattern; first, it refactors code for a single service like authentication to the modular style, then removes the compat library for that service.
Head to the firebase.js
file in the src
directory and modify the v8.x import to look like the following code:
import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/firestore';
With just a few alterations, we’ve updated the app to the v9.x compat. Now, we can start our app with npm start
, and it won’t throw any errors. We should also start the Firebase function locally to expose the API that gets the client secret from Strapi.
In your terminal, change to the functions
directory and run the following command to start the function:
$ firebase emulators:start
In the Login.js
, App.js
, and Header.js
, we used the Firebase authentication service. First, let’s refactor the code in the Login.js
file, where we created the functionality to create a user and sign them in with the Firebase createUserWithEmailAndPassword
and signInWithEmailAndPassword
methods. When we scan through the Login.js
file, we’ll see the following v8.x code:
// src/Login/Login.js const signIn = e => { ... // signIn an existing user with email and password auth .signInWithEmailAndPassword(email, password) .... } const regiter = e => { ... // Create a new user with email and password using firebase auth .createUserWithEmailAndPassword(email, password) .... }
To follow the modular approach, we’ll import the signInWithEmailAndPassword
and createUserWithEmailAndPassword
methods from the auth
module, then update the code. The refactored version will look like the code below:
// src/Login/Login.js import {signInWithEmailAndPassword, createUserWithEmailAndPassword} from 'firebase/auth' ... const signIn = e => { ... // signIn an existing user with email and password signInWithEmailAndPassword(auth, email, password) ... } const regiter = e => { ... // Create a new user with email and password using firebase createUserWithEmailAndPassword(auth, email, password) ... }
Now, let’s refactor the App.js
and Header.js
files. In the App.js
file, we used the onAuthStateChanged
method to monitor changes in the user’s sign in state:
// src/App.js useEffect(() => { auth.onAuthStateChanged(authUser => { ... }) }, [])
The modular v9.x of the code above looks like the following segment:
// src/App.js import {onAuthStateChanged} from 'firebase/auth' ... useEffect(() => { onAuthStateChanged(auth, authUser => { ... }) }, [])
In the Header.js
file, we used the signOut
method to sign out authenticated users:
// src/Header/Header.js const handleAuthentication = () => { ... auth.signOut() ... }
Update the code above to look like the code snippet below:
// src/Header/Header.js import {signOut} from 'firebase/auth' ... const handleAuthentication = () => { ... signOut(auth) ... }
Now that we’re done refactoring all the authentication codes, it’s time to remove the compat library to gain our size benefit. In the firebase.js
file, replace import 'firebase/compat/auth'
and const auth = firebaseApp.auth()
with the following code:
import {getAuth} from 'firebase/auth' ... const auth = getAuth(firebaseApp)
The process for refactoring Cloud Firestore code is similar to what we just did with the authentication codes. We’ll be working with the Payment.js
and Orders.js
files. In Payment.js
, we use Firestore to store the data of users that paid for products on the site. Inside Payment.js
, we’ll find the following v8.x code:
// src/Payment/Payment.js ... db .collection('users') .doc(user?.uid) .collection('orders') .doc(paymentIntent.id) .set({ basket: basket, amount: paymentIntent.amount, created: paymentIntent.created }) ...
To refactor the code, we first have to import the necessary functions, then update the rest of the code. The v9.x of the code above looks like the following:
// src/Payment/Payment.js import {doc, setDoc} from 'firebase/firestore' ... const ref = doc(db, 'users', user?.uid, 'orders', paymentIntent.id) setDoc(ref, { basket: basket, amount: paymentIntent.amount, created: paymentIntent.created }) ...
In the Orders.js
file, we used the onSnapshot
method to get real-time updates of the data in Firestore. the v9.x code looks like the following:
// src/Orders/Orders.js .... db .collection('users') .doc(user?.uid) .collection('orders') .orderBy('created', 'desc') .onSnapshot(snapshot => { setOrders(snapshot.docs.map(doc => ({ id: doc.id, data: doc.data() }))) }) ...
The v9.x equivalent is as follows:
import {query, collection, onSnapshot, orderBy} from 'firebase/firestore' ... const orderedOrders = query(ref, orderBy('created', 'desc')) onSnapshot(orderedOrders, snapshot => { setOrders(snapshot.docs.map(doc => ({ id: doc.id, data: doc.data() }))) }) ...
Now that we’re done refactoring all the Cloud Firestore codes, let’s remove the compat library. In the firebase.js
file, replace import 'firebase/compat/firestore'
and const db = firebaseApp.firestore()
with the following code:
import { getFirestore } from "firebase/firestore"; ... const db = getFirestore(firebaseApp) ...
The final step in upgrading our Amazon clone app to the new modular v9.x syntax is to update the initialization code. In the firebase.js
file, replace import firebase from 'firebase/compat/app';
and const firebaseApp = firebase.initializeApp(firebaseConfig)
with the following functions:
import { initializeApp } from "firebase/app" ... const firebaseApp = initializeApp(firebaseConfig) ...
Now, we’ve successfully upgraded our app to follow the new v9.x modular format.
The new Firebase v9.x SDK provides a faster web experience than its v8.x predecessor, thanks to its modular format. This tutorial introduced the new SDK and explained how to use its compact library to reflector a React app. You should be able to follow the methods and steps outlined in this article to upgrade your own apps to the newest version.
If you’re still having trouble refactoring your React app, be sure to check out the following Firebase support communities:
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]