Published:

React - Alert (Toaster) Notifications

Tutorial built with React 16.12.0, RxJS 6.5.4 and Bootstrap 4.4.1

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 / toaster notification module in React using RxJS. The example has two pages, one with a single alert and the other with multiple alerts displayed in separate sections. Styling is done with Bootstrap 4.4 CSS.

For more info about how to use RxJS to send messages and communicate between React components see React + RxJS - Communicating Between Components with Observable & Subject.

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

Here it is in action:(See on StackBlitz at https://stackblitz.com/edit/react-alerts)


Running the React Alert / Toaster Example Locally

  1. Install NodeJS and NPM from https://nodejs.org.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/react-alert-notifications.
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder, this will build the application and automatically launch it in the browser on the URL http://localhost:8080.

For more info on setting up a React development environment see React - Setup Development Environment.


Adding Alerts / Toaster Notifications to Your React App

To add alerts to your React application you'll need to copy the following files from the example project:

  • _components/Alert.jsx - the alert component is responsible for displaying alerts.
  • _services/alert.service.js - the alert service can be used by any react component or service to send alerts to alert components.
  • _helpers/history.js - the history helper creates and exposes a history object which integrates with the react router and allows the alert component to automatically clear alerts on route changes.


Ensure your project has the required node package dependencies

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

{
    ...
    "dependencies": {
        "history": "^4.10.1",
        "prop-types": "^15.7.2",
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "react-router-dom": "^5.1.2",
        "rxjs": "^6.5.4"
    },
    ...
}


Pass the history helper to the React Router

Inside your react app component (or wherever you have your routes defined), import the history helper and pass it as a parameter to the <Router> component. This hooks the history object up with the react router so the alert component can listen for route changes and automatically clear alerts.

See lines 4 and 9 below from the example react app component.

import React from 'react';
import { Router, Route, Link } from 'react-router-dom';

import { history } from '../_helpers';

class App extends React.Component {
    render() {
        return (
            <Router history={history}>
                ...
            </Router>
        );
    }
}

export { App };


Import and add the <Alert /> component where you want alerts to be displayed

Add the alert component tag wherever you want alert messages to be displayed.

Alert Component Options

  • The alert component accepts optional id and fade attributes:
    • id - used if you want to display multiple alerts in different locations (see the Multiple Alerts page in the example above). 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". Defaults to default-alert.
    • fade - controls if alert messages are faded out when closed. Defaults to true.

The app component in the example /src/app/App.jsx contains a global Alert tag without an id above the Route tags, 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 22 below from the example app show how to add the react alert component.

import React from 'react';
import { Router, Route, Link } from 'react-router-dom';

import { history } from '../_helpers';
import { Alert } from '../_components';
import { Home } from '../home';
import { MultiAlerts } from '../multi-alerts';

class App extends React.Component {
    render() {
        return (
            <Router history={history}>
                {/* nav */}
                <div className="container text-center">
                    <Link to="/" className="btn btn-link">Single Alert</Link>
                    <Link to="/multi-alerts" className="btn btn-link">Multiple Alerts</Link>
                </div>

                {/* main app container */}
                <div className="jumbotron p-4">
                    <div className="container text-center">
                        <Alert />
                        <Route exact path="/" component={Home} />
                        <Route path="/multi-alerts" component={MultiAlerts} />
                    </div>
                </div>
            </Router>
        );
    }
}

export { App };


Displaying Alert / Toaster Notifications in Your React App

Once you've added support for alerts / toaster notifications to your 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 the alert methods 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. Default is false.
    • keepAfterRouteChange - if true prevents the alert from being closed after one route change, this is handy for displaying messages after a redirect such as a successful registration message. Default is false.

Here is the home component from the example app that sends example notification messages to the alert service when each of the buttons is clicked. In a real world application alert notifications can be triggered by any type of event, for example an error from an http request or a success message after a user profile is saved.

import React from 'react';

import { alertService } from '../_services';

class Home extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            autoClose: false,
            keepAfterRouteChange: false
        };

        this.handleChange = this.handleChange.bind(this);
    }
    
    handleChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
        this.setState({ [name]: value });
    }

    render() {
        const { autoClose, keepAfterRouteChange } = this.state;
        return (
            <div>
                <h1>React Alerts</h1>
                <button className="btn btn-success m-1" onClick={() => alertService.success('Success!!', { autoClose, keepAfterRouteChange })}>Success</button>
                <button className="btn btn-danger m-1" onClick={() => alertService.error('Error :(', { autoClose, keepAfterRouteChange })}>Error</button>
                <button className="btn btn-info m-1" onClick={() => alertService.info('Some info....', { autoClose, keepAfterRouteChange })}>Info</button>
                <button className="btn btn-warning m-1" onClick={() => alertService.warn('Warning: ...', { autoClose, keepAfterRouteChange })}>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={autoClose} onChange={this.handleChange} />
                        <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={keepAfterRouteChange} onChange={this.handleChange} />
                        <label htmlFor="keepAfterRouteChange">Keep displaying after one route change</label>
                    </div>
                </div>
            </div>
        );
    }
}

export { Home };

 


 

Breakdown of the React Alert / Toaster Notification Code

Below is a breakdown of the pieces of code used to implement the alert / toaster notification example in React, 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.


React Alert Service

The alert service (/src/app/_services/alert.service.js) acts as the bridge between any component in an React application and the alert component that actually 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 application.

The service uses the RxJS Observable and Subject classes to enable communication with other 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';

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

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

export const alertType = {
    success: 'success',
    error: 'error',
    info: 'info',
    warning: 'warning'
}

// 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;
    alertSubject.next(alert);
}

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


React Alert Component

The alert component (/src/app/_components/Alert.jsx) controls the adding & removing of alerts in the UI, it maintains an array of alerts that are rendered in the render() method.

The componentDidMount() method subscribes 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. This method also calls history.listen() to register a route change listener so it can automatically clear alerts on route changes.

The componentWillUnmount() method unsubscribes from the alert service and "unlistens" to the history object when the component is destroyed to prevent memory leaks from orphaned subscriptions.

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

The cssClass() method returns a corresponding bootstrap alert class for each of the alert types, if you're using something other than bootstrap you can change the CSS classes returned to suit your application.

The render() renders an alert message for each alert in the alerts array. Bootstrap 4 is used for styling the alerts / toaster notifications in the example, you can change the HTML and CSS classes in this template to suit your application if you're not using Bootstrap.

import React from 'react';
import PropTypes from 'prop-types';

import { history } from '../_helpers';
import { alertService, alertType } from '../_services';

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

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

class Alert extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            alerts: []
        };
    }

    componentDidMount() {
        // subscribe to new alert notifications
        this.subscription = alertService.onAlert(this.props.id)
            .subscribe(alert => {
                // clear alerts when an empty alert is received
                if (!alert.message) {
                    // filter out alerts without 'keepAfterRouteChange' flag
                    const alerts = this.state.alerts.filter(x => x.keepAfterRouteChange);

                    // remove 'keepAfterRouteChange' flag on the rest
                    alerts.forEach(x => delete x.keepAfterRouteChange);

                    this.setState({ alerts });
                    return;
                }

                // add alert to array
                this.setState({ alerts: [...this.state.alerts, alert] });

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

        // clear alerts on location change
        this.historyUnlisten = history.listen(() => {
            alertService.clear(this.props.id);
        });
    }

    componentWillUnmount() {
        // unsubscribe & unlisten to avoid memory leaks
        this.subscription.unsubscribe();
        this.historyUnlisten();
    }

    removeAlert(alert) {
        if (this.props.fade) {
            // fade out alert
            const alertWithFade = { ...alert, fade: true };
            this.setState({ alerts: this.state.alerts.map(x => x === alert ? alertWithFade : x) });

            // remove alert after faded out
            setTimeout(() => {
                this.setState({ alerts: this.state.alerts.filter(x => x !== alertWithFade) })
            }, 250);
        } else {
            // remove alert
            this.setState({ alerts: this.state.alerts.filter(x => x !== alert) })
        }
    }

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

        const classes = ['alert', 'alert-dismissable'];
                
        const alertTypeClass = {
            [alertType.success]: 'alert alert-success',
            [alertType.error]: 'alert alert-danger',
            [alertType.info]: 'alert alert-info',
            [alertType.warning]: 'alert alert-warning'
        }

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

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

        return classes.join(' ');
    }

    render() {
        const { alerts } = this.state;
        if (!alerts.length) return null;
        return (
            <div className="m-3">
                {alerts.map((alert, index) =>
                    <div key={index} className={this.cssClasses(alert)}>
                        <a className="close" onClick={() => this.removeAlert(alert)}>&times;</a>
                        <span dangerouslySetInnerHTML={{__html: alert.message}}></span>
                    </div>
                )}
            </div>
        );
    }
}

Alert.propTypes = propTypes;
Alert.defaultProps = defaultProps;
export { Alert };

 

Subscribe or Follow Me For Updates

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

 


Supported by