Published:

Next.js - Alert (Toaster) Notifications

Tutorial built with Next.js 11.1.0 and RxJS 7.3.0

Other versions available:

Alert messages (a.k.a. toaster notifications) are an extremely common requirement in web applications for displaying status messages to the user e.g. error, success, warning and info alerts.

This tutorial shows how to implement a simple reusable alert component in a Next.js front-end app using React and RxJS. The example has two pages, one with a single alert component and the other with multiple alerts displayed in separate sections of the page.

RxJS

RxJS subjects and observables are used by the alert service to enable any react component to send and receive alerts in the Next.js application. For more info on using React with RxJS see React + RxJS - Communicating Between Components with Observable & Subject.

Bootstrap CSS

Bootstrap CSS is used in the example to display alerts and for general styling, to change the CSS classes for alerts simply update the cssClasses() function of the alert component. For more info on bootstrap alerts you can find the official docs at https://getbootstrap.com/docs/4.5/components/alerts/, you can ignore the javascript behavior section since the example uses React to handle behavior.

Code on GitHub

The example project is available on GitHub at https://github.com/cornflourblue/next-js-alert-notifications.

Here it is in action: (See on CodeSandbox at https://codesandbox.io/s/next-js-alert-toaster-notifications-eqo82)


Run the Next.js Alerts Example Locally

  1. Install Node.js and npm from https://nodejs.org.
  2. Download or clone the Next.js project source code from https://github.com/cornflourblue/next-js-alert-notifications
  3. Install all required npm packages by running npm install or npm i from the command line in the project root folder (where the package.json is located).
  4. Start the app by running npm run dev from the command line in the project root folder, this will compile the app and start the Next.js server.
  5. Open to the app at the URL http://localhost:3000.

NOTE: You can also start the Next.js alerts app directly with the Next.js CLI command npx next dev. For more info on the Next.js CLI see https://nextjs.org/docs/api-reference/cli.


Adding Alerts to Your Next.js App

To add alerts to your Next.js app you just need to copy the following files from the example:

  • components/Alert.jsx - the alert component is responsible for displaying alerts in the Next.js app.
  • services/alert.service.js - the alert service is used by any react component or service to send alerts to alert components.


Ensure your project has the required node package dependencies

This is the list of dependencies from the example application that are required by the Next.js alert component and service:

{
    ...
    "dependencies": {
        "next": "^11.1.0",
        "prop-types": "^15.7.2",
        "react": "^17.0.2",
        "react-dom": "^17.0.2",
        "rxjs": "^7.3.0"
    },
    ...
}


Import and add the <Alert /> component

Import and add the Alert component wherever you want alert messages to be displayed in your Next.js app.

Alert Component Options

The alert component accepts the following optional attributes:

  • id - used if you want to display multiple alerts in different locations (see the Multiple Alerts page in the example). An alert component with an id attribute will display any messages sent to the alert service with a matching id, e.g. alertService.error('something broke!', { id: 'left-alert' }); will send an error message to the alert component with id="left-alert". The default value for id is 'default-alert'.
  • fade - controls if alert messages are faded out when closed. The default value is true.


Next.js App Component

The Next.js app component in the example (/pages/_app.js) contains a global Alert tag without an id above the Component tag, this alert displays any messages sent to the alert service without an id specified, e.g. alertService.success('you won!'); will send a success message to the global alert without an id.

Lines 5 and 23 below from the example Next.js app component show how to import and add an alert component to a page.

import Head from 'next/head';

import 'styles/globals.css';

import { Nav, Alert } from 'components';

export default App;

function App({ Component, pageProps }) {
    return (
        <>
            <Head>
                <title>Next.js - Alert (Toaster) Notifications</title>
                
                {/* eslint-disable-next-line @next/next/no-css-tags */}
                <link href="//netdna.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" />
            </Head>

            <div className='app-container bg-light'>
                <Nav />
                <div className="p-4">
                    <div className="container">
                        <Alert />
                        <Component {...pageProps} />
                    </div>
                </div>
            </div>
       </>
    );
}


Trigger Alert / Toaster Notifications in your Next.js App

Once you've added support for alerts / toaster notifications to your Next.js app by following the previous steps, you can trigger alert notifications from any component in your application by simply importing the alert service and calling one of it's methods for displaying different types of alerts: success(), error(), info() and warn().

Alert Service Options

  • The first parameter to each alert method is a string for the alert message which can be a plain text string or HTML
  • The second parameter is an optional options object that supports an autoClose boolean property and keepAfterRouteChange boolean property:
    • autoClose - if true tells the alert component to automatically close the alert after three seconds. The default value is true.
    • keepAfterRouteChange - if true keeps the alert displaying after one route change, this is handy for showing an alert after a redirect such as after successful user registration. The default value is false.


Next.js Home Component

Below is the home component (/pages/index.jsx) from the Next.js example app that sends a notification message to the alert service when each of the buttons is clicked that gets displayed by the default <Alert /> component in the Next.js app component above (/pages/_app.js). In a real world application an alert notification can be triggered by any type of event not just a button click, for example an error during an HTTP request or a success message after user registers a new account.

import { useState } from 'react';

import { alertService } from 'services';

export default Home;

function Home() {
    const [options, setOptions] = useState({
        autoClose: false,
        keepAfterRouteChange: false
    });

    function handleOptionChange(e) {
        const { name, checked } = e.target;
        setOptions(options => ({ ...options, [name]: checked }));
    }

    return (
        <div>
            <h1 className="mb-3">Next.js - Single Alert</h1>
            <button className="btn btn-success m-1" onClick={() => alertService.success('Success!!', options)}>Success</button>
            <button className="btn btn-danger m-1" onClick={() => alertService.error('Error :(', options)}>Error</button>
            <button className="btn btn-info m-1" onClick={() => alertService.info('Some info....', options)}>Info</button>
            <button className="btn btn-warning m-1" onClick={() => alertService.warn('Warning: ...', options)}>Warn</button>
            <button className="btn btn-outline-dark m-1" onClick={() => alertService.clear()}>Clear</button>
            <div className="form-group mt-2">
                <div className="form-check">
                    <input type="checkbox" className="form-check-input" name="autoClose" id="autoClose" checked={options.autoClose} onChange={handleOptionChange} />
                    <label htmlFor="autoClose">Auto close alert after three seconds</label>
                </div>
                <div className="form-check">
                    <input type="checkbox" className="form-check-input" name="keepAfterRouteChange" id="keepAfterRouteChange" checked={options.keepAfterRouteChange} onChange={handleOptionChange} />
                    <label htmlFor="keepAfterRouteChange">Keep displaying after one route change</label>
                </div>
            </div>
        </div>
    );
}

 


 

Next.js Alert / Toaster Notification Code Explained

Below is a breakdown of the pieces of code used to implement the alert / toaster notification example in Next.js, you don't need to know the details of how it all works to use the alerts in your project, it's only if you're interested in the nuts and bolts or if you want to modify the code or behaviour.


Next.js Alert Service

The alert service (/services/alert.service.js) acts as the bridge between any component in the front-end React application and the alert component that displays the alert / toaster messages. It contains methods for sending, clearing and subscribing to alert messages.

The AlertType object defines the types of alerts allowed in the Next.js app.

The service uses the RxJS Observable and Subject classes to enable communication with other React components, for more information on how this works see React + RxJS - Communicating Between Components with Observable & Subject.

import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';

export const alertService = {
    onAlert,
    success,
    error,
    info,
    warn,
    alert,
    clear
};

export const AlertType = {
    Success: 'Success',
    Error: 'Error',
    Info: 'Info',
    Warning: 'Warning'
};

const alertSubject = new Subject();
const defaultId = 'default-alert';

// enable subscribing to alerts observable
function onAlert(id = defaultId) {
    return alertSubject.asObservable().pipe(filter(x => x && x.id === id));
}

// convenience methods
function success(message, options) {
    alert({ ...options, type: AlertType.Success, message });
}

function error(message, options) {
    alert({ ...options, type: AlertType.Error, message });
}

function info(message, options) {
    alert({ ...options, type: AlertType.Info, message });
}

function warn(message, options) {
    alert({ ...options, type: AlertType.Warning, message });
}

// core alert method
function alert(alert) {
    alert.id = alert.id || defaultId;
    alert.autoClose = (alert.autoClose === undefined ? true : alert.autoClose);
    alertSubject.next(alert);
}

// clear alerts
function clear(id = defaultId) {
    alertSubject.next({ id });
}


Next.js Alert Component

The alert component (/components/Alert.jsx) controls the adding & removing of alerts in the UI of the Next.js app, it maintains an array of alerts that are rendered in the JSX template returned by the React component.

The useEffect() hook is used to subscribe to the observable returned from the alertService.onAlert() method, this enables the alert component to be notified whenever an alert message is sent to the alert service and add it to the alerts array for display. Sending an alert with an empty message to the alert service tells the alert component to clear the alerts array. The useEffect() hook is also used to register a route change listener by calling router.events.on('routeChangeStart', clearAlerts) which automatically clears alerts on route changes.

The empty dependency array [] passed as a second parameter to the useEffect() hook causes the react hook to only run once when the component mounts, similar to the componentDidMount() method in a traditional react class component. The function returned from the useEffect() hook cleans up the subscribtions when the component unmounts, similar to the componentWillUnmount() method in a traditional react class component. The mounted ref variable is used to keep track of when the component is mounted/unmounted to avoid making state changes after the component is unmounted (e.g. when an alert auto closes after 3 seconds).

The removeAlert() method removes the specified alert object from the array, it allows individual alerts to be closed in the Next.js app.

The cssClasses() method returns corresponding bootstrap alert classes for each of the alert types, if you're using something other than bootstrap you could change the CSS classes here to suit your application.

The returned JSX template renders an alert message for each alert in the alerts array.

import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/router';
import PropTypes from 'prop-types';

import { alertService, AlertType } from 'services';

export { Alert };

Alert.propTypes = {
    id: PropTypes.string,
    fade: PropTypes.bool
};

Alert.defaultProps = {
    id: 'default-alert',
    fade: true
};

function Alert({ id, fade }) {
    const mounted = useRef(false);
    const router = useRouter();
    const [alerts, setAlerts] = useState([]);

    useEffect(() => {
        mounted.current = true;

        // subscribe to new alert notifications
        const subscription = alertService.onAlert(id)
            .subscribe(alert => {
                // clear alerts when an empty alert is received
                if (!alert.message) {
                    setAlerts(alerts => {
                        // filter out alerts without 'keepAfterRouteChange' flag
                        const filteredAlerts = alerts.filter(x => x.keepAfterRouteChange);
                            
                        // remove 'keepAfterRouteChange' flag on the rest
                        return omit(filteredAlerts, 'keepAfterRouteChange');
                    });
                } else {
                    // add alert to array with unique id
                    alert.itemId = Math.random();
                    setAlerts(alerts => ([...alerts, alert]));

                    // auto close alert if required
                    if (alert.autoClose) {
                        setTimeout(() => removeAlert(alert), 3000);
                    }
                }
            });


        // clear alerts on location change
        const clearAlerts = () => alertService.clear(id);
        router.events.on('routeChangeStart', clearAlerts);

        // clean up function that runs when the component unmounts
        return () => {
            mounted.current = false;

            // unsubscribe to avoid memory leaks
            subscription.unsubscribe();
            router.events.off('routeChangeStart', clearAlerts);
        };

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    function omit(arr, key) {
        return arr.map(obj => {
            const { [key]: omitted, ...rest } = obj;
            return rest;
        });
    }

    function removeAlert(alert) {
        if (!mounted.current) return;

        if (fade) {
            // fade out alert
            setAlerts(alerts => alerts.map(x => x.itemId === alert.itemId ? { ...x, fade: true } : x));

            // remove alert after faded out
            setTimeout(() => {
                setAlerts(alerts => alerts.filter(x => x.itemId !== alert.itemId));
            }, 250);
        } else {
            // remove alert
            setAlerts(alerts => alerts.filter(x => x.itemId !== alert.itemId));
        }
    };

    function cssClasses(alert) {
        if (!alert) return;

        const classes = ['alert', 'alert-dismissable'];

        const alertTypeClass = {
            [AlertType.Success]: 'alert-success',
            [AlertType.Error]: 'alert-danger',
            [AlertType.Info]: 'alert-info',
            [AlertType.Warning]: 'alert-warning'
        }

        classes.push(alertTypeClass[alert.type]);

        if (alert.fade) {
            classes.push('fade');
        }

        return classes.join(' ');
    }

    if (!alerts.length) return null;

    return (
        <div>
            {alerts.map((alert, index) =>
                <div key={index} className={cssClasses(alert)}>
                    <a className="close" onClick={() => removeAlert(alert)}>&times;</a>
                    <span dangerouslySetInnerHTML={{ __html: alert.message }}></span>
                </div>
            )}
        </div>
    );
}

 


Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter, Facebook or GitHub to be notified when I post new content.

Other than coding...

I'm currently attempting to travel around Australia by motorcycle with my wife Tina on a pair of Royal Enfield Himalayans. You can follow our adventures on YouTube, Instagram and Facebook.


Need Some NextJS Help?

Search fiverr to find help quickly from experienced NextJS developers.



Supported by