Kapeel Kokane Coder by day, Content creator by night, Learner at heart!

Push notifications with React and Firebase

6 min read 1885

React and Firebase Logos

Introduction

Firebase was a company that Google acquired in 2014. Since then, Google has made several enhancements to the platform to the point that it now pitches Firebase as its one-stop backend-as-a-service solution for mobile apps. Among several Google solutions like centralized authentication, realtime databases, cloud functions (which is Google’s equivalent of AWS lambda) that are a part of the Firebase umbrella, there is one that is picked as a go-to solution for managing app notifications. That is Firebase Cloud Messaging (FCM), formerly Google Cloud Messaging (GCM). In today’s article, we will look into the process to enable the push-notifications feature in our front-end React application.

The React app

Basic app

For the purpose of this article, we will be using a skeleton React app created by the create-react-app npm package. So, let’s create a folder named firebase-notifications and inside of its, lets run the command (make sure you have npm and npx installed):

npx create-react-app fire_client

This creates a basic React app. To run the app, lets cd into the folder and run npm run start so that the app starts on localhost and we get this familiar page:

React App Starting on localhost

Library integration

Now, let’s add the dependencies for integrating the client-side functionality of Firebase Cloud Messaging and react-bootstrap for getting a few Bootstrap-based React UI components:

npm install --save firebase react-bootstrap bootstrap

Bootstrap setup

Once that is done, let’s change the src/App.js file like so:

import logo from './logo.svg';
import './App.css';
import {useState} from 'react';
import { getToken, onMessageListener } from './firebase';
import {Button, Row, Col, Toast} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';

function App() {

  getToken();

  const [show, setShow] = useState(false);

  onMessageListener().then(message => {
    setShow(true);
  }).catch(err => console.log('failed: ', err));

  return (
    <div className="App">
        <Toast onClose={() => setShow(false)} show={show} delay={3000} autohide animation style={{
          position: 'absolute',
          top: 20,
          right: 20,
        }}>
          <Toast.Header>
            <img
              src="holder.js/20x20?text=%20"
              className="rounded mr-2"
              alt=""
            />
            <strong className="mr-auto">Notification</strong>
            <small>12 mins ago</small>
          </Toast.Header>
          <Toast.Body>There are some new updates that you might love!</Toast.Body>
        </Toast>
      <header className="App-header">


        <img src={logo} className="App-logo" alt="logo" />
        <Button onClick={() => setShow(true)}>Show Toast</Button>
      </header>


    </div>
  );
}

export default App;

We’ve imported the bootstrap CSS in App.js and also, there’s a show toast button that brings up a toast notification which auto-hides after three seconds. Here’s how that looks:

Toast Notification

With that in place, we can now move to the Firebase setup.

Firebase setup

Project creation

Login/sign up into the Firebase console and click on Add project. Follow the steps to add the project:

We made a custom demo for .
No really. Click here to check it out.

  • Give the project an appropriate name
  • Enable/disable analytics based on your preference
  • Wait for the setup to complete

Once done, redirect to the project home screen which should look something like this:

Project Home Screen

Project link

Now we need to create a link between the Firebase project and the frontend React app that we just now created. We would be doing that using the Firebase project config. Out of the IOS, Android, and web options that are visible in the previous screenshot, click on the web (</>) icon. This takes us to the Register app UI:

Register App Page

Assign a nickname to the React app that we created in the earlier step and click on the Register app button. Wait for a while as the screen generates a config which we will be soon integrating in the React app. This is what the final generated config should look like:

Final Configuration

The firebaseConfig object is the one that we will be integrating into our React app which will link it to this particular firebase project.

Linking projects

React and Firebase Projects Linked Together

Let’s create a new file called firebase.js in our React codebase and add these lines of code there:

import firebase from 'firebase/app';
var firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);

Make sure to replace the firebaseConfig with the one that got generated in the previous step. Importing the firebase.js file in App.js now links both the projects together. Let’s now move to setting up cloud messaging on Firebase.

Integrate cloud messaging

As a next step, we need to generate a web push certificate key. Navigate to the cloud messaging tab for your project and scroll to the Web configuration section. Under Web push certificates, click on Generate key pair. Note down the key that gets generated.

Back in the firebase.js file, we now need to enable messaging. Add this line to the imports:

import 'firebase/messaging';

And then, we can access the messaging object from the firebase object like so:

const messaging = firebase.messaging();

Notification permissions and registering client

In order to send push notifications to the browser, we first need to get permission from the user. When we do that, it opens the Enable Notifications on this site? popup that you might have seen on some sites. The way to initiate that request is by calling the getToken method that firebase provides.

Before that, let’s add a variable to the state of the App.js file which will keep track of whether we have access to the notifications or not:

const [isTokenFound, setTokenFound] = useState(false);
getToken(setTokenFound);

// inside the jsx being returned:
{isTokenFound && <h1> Notification permission enabled πŸ‘πŸ» </h1>}
{!isTokenFound && <h1> Need notification permission ❗️ </h1>}

Let’s add the getToken function to firebase.js:

export const getToken = (setTokenFound) => {
  return messaging.getToken({vapidKey: 'GENERATED_MESSAGING_KEY'}).then((currentToken) => {
    if (currentToken) {
      console.log('current token for client: ', currentToken);
      setTokenFound(true);
      // Track the token -> client mapping, by sending to backend server
      // show on the UI that permission is secured
    } else {
      console.log('No registration token available. Request permission to generate one.');
      setTokenFound(false);
      // shows on the UI that permission is required 
    }
  }).catch((err) => {
    console.log('An error occurred while retrieving token. ', err);
    // catch error while creating client token
  });
}

Notice how we are passing the setTokenFound function to the getToken function so that we can keep a track of whether we got the client token (i.e. whether notification permission was provided) or not. When we save and execute the code now, we get a popup requesting for notifications:

Popup Requesting Notifications

The messaging.getToken displays the UI for notification, waits for a user input and upon allowing, assigns a unique token to the client that can be seen in the console:

Unique Token

Configuring message listeners

Background listener

Now that we have the notification permission and a reference to the client token on the browser side, the next step is to add a listener to the incoming push notification that is directed towards the client. We do that by adding a firebase-messaging-sw.js service-worker file in the public folder in our react app and add this code:

// Scripts for firebase and firebase messaging
importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js');

// Initialize the Firebase app in the service worker by passing the generated config
var firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

firebase.initializeApp(firebaseConfig);

// Retrieve firebase messaging
const messaging = firebase.messaging();

messaging.onBackgroundMessage(function(payload) {
  console.log('Received background message ', payload);

  const notificationTitle = payload.notification.title;
  const notificationOptions = {
    body: payload.notification.body,
  };

  self.registration.showNotification(notificationTitle,
    notificationOptions);
});

This code will handle all the notifications that reach the application when it’s not in the foreground (visible in the current browser tab).

Foreground listener

To take care of the case where the app is active in foreground, we need to add this code to the firebase.js file:

export const onMessageListener = () =>
  new Promise((resolve) => {
    messaging.onMessage((payload) => {
      resolve(payload);
    });
});

We also need to import this in App.js and add logic to create the notification out of the parsed payload which looks something like this:

function App() {

  const [show, setShow] = useState(false);
  const [notification, setNotification] = useState({title: '', body: ''});
  const [isTokenFound, setTokenFound] = useState(false);
  getToken(setTokenFound);

  onMessageListener().then(payload => {
    setShow(true);
    setNotification({title: payload.notification.title, body: payload.notification.body})
    console.log(payload);
  }).catch(err => console.log('failed: ', err));

  return (
    <div className="App">
        <Toast onClose={() => setShow(false)} show={show} delay={3000} autohide animation style={{
          position: 'absolute',
          top: 20,
          right: 20,
          minWidth: 200
        }}>
          <Toast.Header>
            <img
              src="holder.js/20x20?text=%20"
              className="rounded mr-2"
              alt=""
            />
            <strong className="mr-auto">{notification.title}</strong>
            <small>just now</small>
          </Toast.Header>
          <Toast.Body>{notification.body}</Toast.Body>
        </Toast>
      <header className="App-header">
        {isTokenFound && <h1> Notification permission enabled πŸ‘πŸ» </h1>}
        {!isTokenFound && <h1> Need notification permission ❗️ </h1>}
        <img src={logo} className="App-logo" alt="logo" />
        <Button onClick={() => setShow(true)}>Show Toast</Button>
      </header>


    </div>
  );
}

And we’re all set to receive both foreground and background notifications in our React app.

Testing push notifications

We can now test whether our notifications are functional by visiting the cloud messaging section of our app and triggering a test notification:

  • Under the Notifications tab, click on New notification
  • Enter the Notification title and Notification text
  • Under the Device preview section, click on Send test message
  • In the popup that opens, enter the client token that is logged in the console as the FCM registration token and press the + button
  • Make sure that the FCM token is checked and click on “Test”

Test Device UI

If the React app is open in a foreground browser tab, you should see the message popup with the contents that were just filled up. And in case the tab was in background, you should see a default notification which looks something like this:

Default Notification

Once this testing is done and the message appears as per your liking, you can continue with the process of configuring the push notification by selecting the app and creating a campaign. In that case, all the users of that app would receive a push notification similar to the one tested just now.

Conclusion

We have covered the steps involved in integrating Firebase into a React app and configuring both foreground and background messages. In this flow, we are triggering the notifications via the cloud messaging panel in Firebase. An alternate method would be to store a client-to-token mapping in a backend server and when the notification is enabled then later trigger the notification using the firebase-admin library by targeting those particular tokens. That would allow for a more fine-grained control over notifications. But as far as sending push notifications to client side devices (iOS, Android, or web) is concerned, using the Firebase platform is preferred. Do give it a try the next time you come across such a requirement in your project. Find the full code on this GitHub repository.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps β€” .

Kapeel Kokane Coder by day, Content creator by night, Learner at heart!

One Reply to “Push notifications with React and Firebase”

  1. Hey, thanks for this guide.

    On Windows and Android this works as expected, however, on Safari (iOS) this just gives a white / blank screen.

    Obviously, Safari does not support Web push notifications but is there a way to exclude Messaging initialization if it’s not supported? As currently I suppose this is the root cause.

    What would be the required code adjustments to do this, can you suggest anything?

    Thanks,
    John

Leave a Reply