DEV Community

Christianaojo
Christianaojo

Posted on

Build Refactor: A React app with the new Firebase v9.x Web SDK

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!

Prerequisites

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.

Introducing Firebase v9.x Web SDK

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
});
Enter fullscreen mode Exit fullscreen mode

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
});

Enter fullscreen mode Exit fullscreen mode

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.

Firebase compatibility library

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';
Enter fullscreen mode Exit fullscreen mode

We’ll take advantage of the library when we refactor our Amazon clone app.

Firebase Web SDK v9.x benefits

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.

Firebase Tweet v9 80 Percent Size

Image description

Setting up our React app for refactoring

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:

Image description

Amazon Clone App

Feel free to play around with the app to better understand what we’re working on in this article.

Setting up the Amazon clone app

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
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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 
Enter fullscreen mode Exit fullscreen mode

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.

React app structure

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
Enter fullscreen mode Exit fullscreen mode

We’ll only be working with the files that use Firebase services, firebase, App.js, Header.js, Login.js, Payment.js, and Orders.js,

Refactoring the Amazon clone to a modular approach

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.

Update imports to the v9.x compat library

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';
Enter fullscreen mode Exit fullscreen mode

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

Refactoring authentication codes

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)
      ....
  }  
Enter fullscreen mode Exit fullscreen mode

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)
  ...
}  
Enter fullscreen mode Exit fullscreen mode

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 => {
    ...
  })
}, [])
Enter fullscreen mode Exit fullscreen mode

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 => {
    ...
  })
}, [])
Enter fullscreen mode Exit fullscreen mode

In the Header.js file, we used the signOut method to sign out authenticated users:

// src/Header/Header.js
const handleAuthentication = () => {
  ...
     auth.signOut()
  ...
}
Enter fullscreen mode Exit fullscreen mode

Update the code above to look like the code snippet below:

// src/Header/Header.js
import {signOut} from 'firebase/auth'
...
const handleAuthentication = () => {
  ...
    signOut(auth)
  ...
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Refactoring Cloud Firestore codes

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
  })
...
Enter fullscreen mode Exit fullscreen mode

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
})
...
Enter fullscreen mode Exit fullscreen mode

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()
     })))
  })
...
Enter fullscreen mode Exit fullscreen mode

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()
     })))
  })
...
Enter fullscreen mode Exit fullscreen mode

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)
...
Enter fullscreen mode Exit fullscreen mode

Update initialization code

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)
...
Enter fullscreen mode Exit fullscreen mode

Now, we’ve successfully upgraded our app to follow the new v9.x modular format.

Conclusion
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.

Top comments (0)