Nur Islam Sport Programmer

Adding spinners and notifications to your React app

10 min read 2968

Adding Spinners And Notifications To Your React App

To make your web projects more interactive and user-friendly, you may find you want to add some additional features like notifications, or a spinner that shows a loading state.

Today we are going to explore how we can implement those features. Rather than simply showing some examples, we will focus on how we can integrate them into a full-stack project.

Before we start…

So what exactly are we going to discuss here?

  1. We will review all the necessary steps needed to add a spinner (to indicate loading, for example) to our project.
  2. We will manage notifications using only one ‘Notification Container’ in our project and learn how we can use them to display with proper message. We are going to discuss two different npm packages for this so that we can compare them a bit.

Here I’ll use an existing MERN project to which we can add those features to see the outcome. I’m not going to discuss this existing project in depth since our aim here is just to show the implementation and integration of the above features.

In fact, we already have a good tutorial for that project in two parts, which explain everything you’ll need to understand. If you want, you can give the first part a read here. The second part is available here.

Resources

Here is the GitHub repo for the server side of the project, and here is the repo for the client side. Just clone or download them, whatever you prefer, and run them either by following the instructions provided in the README.md file or those provided below.

To start running the server, make sure you are in the MERN_A_to_Z/ directory and type the following commands:

$ npm install
$ npm run app

NOTE: We just need the running server. After that, we don’t have to make any changes there.

To start the client server, make sure you are in the MERN_A_to_Z_Client/mern_a_to_z_client/ directory and type the following commands:

$ npm install
$ npm start

NOTE: We’ll work strictly in our client project; all changes will occur there.

Now that you have both the server and client sides of the project running, visit http://localhost://3000 to see the project live.

Spinner setting

Here I’m going to add a loading spinner to our existing MERN project. We will update our ShowBookList.js file to add a loading spinner in the ShowBookList component.

So, create a folder named common inside the component folder. The path should like this: MERN_A_to_Z_Client/mern_a_to_z_client/src/components/common. Now, inside the common folder, create a file named Spinner.js and add a .gif file for a loading spinner.

You can find different kinds of .gif files free all over the internet, or you can use the one provided with the source code.

Now, update your Spinner.js with the following code:

import React from 'react';
import spinner from './spinner.gif';

export default () => {
  return (
    <div>
      <img
        src={spinner}
        style={{ width: '340px', margin: 'auto', display: 'block' }}
        alt="Loading..."
      />
    </div>
  );
};

NOTE: Here we import the .gif file (import spinner from './spinner.gif';). If you’re using your own, different spinner file, replace spinner.gif with the filename you want to add.

Now, update your ShowBookList.js file with this:

import React, { Component } from 'react';
import '../App.css';
import axios from 'axios';
import { Link } from 'react-router-dom';
import BookCard from './BookCard';

// spinner file
import Spinner from './common/Spinner';

class ShowBookList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      books: []
    };
  }

  componentDidMount() {
    axios
      .get('http://localhost:8082/api/books')
      .then(res => {
        this.setState({
          books: res.data,
          loading: false
        })
      })
      .catch(err =>{
        console.log('Error from ShowBookList');
      })
  };


  render() {
    const books = this.state.books;
    const loading = this.state.loading;
    // console.log("PrintBook: " + books);

    let bookList;

    if(books === null || loading) {
      bookList = <Spinner />;
    } else {
      if (books) {
        bookList = books.map((book, k) =>
          <BookCard book={book} key={k} />
        );
      } else {
        bookList = <h3 className="text-center">there is no book recored!</h3>;
      }
    }

    const BOOKLIST = (
      <div className="list">
        {bookList}
      </div>
    );

    const SPINNER = (
      <div>
        {bookList}
      </div>
    );

    return (
      <div className="ShowBookList">
        <div className="container">
          <div className="row">
            <div className="col-md-12">
              <br />
              <h2 className="display-4 text-center">Books List</h2>
            </div>

            <div className="col-md-11">
              <Link to="/create-book" className="btn btn-outline-warning float-right">
                + Add New Book
              </Link>
              <br />
              <br />
              <hr />
            </div>

          </div>

          {loading ? SPINNER : BOOKLIST}

        </div>
      </div>
    );
  }
}

export default ShowBookList;

Here we import our Spinner component from common/Spinner.js and use some logic inside the render function for assigning a value to bookList. We also added a loading state initially set to false inside the constructor.

You don’t need to follow the same logic; you can write in your own way, and obviously, it will be different depending on your project type.

Now, run the project and visit: http://localhost:3000/

You will see a Loading spinner like the following one for a very short period of time. This is the delay time of fetching data through the API. That means this spinner will be shown until the state value of books (this.state.books) is null or loading (this.state.loading) is true.

Books List With Loading Spinner

You can adjust the background color of the spinner, or you can, of course, use a customized spinner. Here my goal was just to show where and when we can use spinners and how can we set up a spinner.

Configuring notifications with react-notifications

Now I’ll show how we can handle notifications in our React project. First we’ll be using react-notifications, which, as its name suggests, is a notification component for React.

Package installation

Go to the client project directory (MERN_A_to_Z_Client/mern_a_to_z_client/) and install the following npm package:

$ npm install --save react-notifications

Run the project again.

Setting up the notification container

Now update the App.js file. Import NotificationContainer from react-notifications and the notifications.css file.

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import './App.css';

import CreateBook from './components/CreateBook';
import ShowBookList from './components/ShowBookList';
import ShowBookDetails from './components/ShowBookDetails';
import UpdateBookInfo from './components/UpdateBookInfo';

// React Notification
import 'react-notifications/lib/notifications.css';
import { NotificationContainer } from 'react-notifications';

class App extends Component {
  render() {
    return (
      <Router>
        <div>
          <Route exact path='/' component={ShowBookList} />
          <Route path='/create-book' component={CreateBook} />
          <Route path='/edit-book/:id' component={UpdateBookInfo} />
          <Route path='/show-book/:id' component={ShowBookDetails} />
          <NotificationContainer />
        </div>
      </Router>
    );
  }
}

export default App;

So far, so good — we have completed our setup for NotificationContainer.

NOTE: Use only one NotificationContainer component in the app.

Now it’s time to pass notifications from different components to display their message.

Setting notifications from components

Here you just have to import the NotificationManager from react-notifications. After that, you are ready to pass notifications through NotificationManager.

Look at the changes I have made in the CreateBook.js file to pass notifications from the CreateBook component.

Open CreateBook.js and update it with the following code:

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import '../App.css';
import axios from 'axios';

// React Notification
import { NotificationManager } from 'react-notifications';

class CreateBook extends Component {
  constructor() {
    super();
    this.state = {
      title: '',
      isbn:'',
      author:'',
      description:'',
      published_date:'',
      publisher:''
    };
  }

  onChange = e => {
    this.setState({ [e.target.name]: e.target.value });
  };

  onSubmit = e => {
    e.preventDefault();

    const data = {
      title: this.state.title,
      isbn: this.state.isbn,
      author: this.state.author,
      description: this.state.description,
      published_date: this.state.published_date,
      publisher: this.state.publisher
    };

    axios
      .post('http://localhost:8082/api/books', data)
      .then(res => {
        this.setState({
          title: '',
          isbn:'',
          author:'',
          description:'',
          published_date:'',
          publisher:''
        })
        this.props.history.push('/');
        NotificationManager.success('You have added a new book!', 'Successful!', 2000);
      })
      .catch(err => {
        // console.log("Error in CreateBook!");
        NotificationManager.error('Error while Creating new book!', 'Error!');
      })
  };

  render() {
    return (
      <div className="CreateBook">
        <div className="container">
          <div className="row">
            <div className="col-md-8 m-auto">
              <br />
              <Link to="/" className="btn btn-outline-warning float-left">
                  Show BooK List
              </Link>
            </div>
            <div className="col-md-8 m-auto">
              <h1 className="display-4 text-center">Add Book</h1>
              <p className="lead text-center">
                  Create new book
              </p>

              <form noValidate onSubmit={this.onSubmit}>
                <div className='form-group'>
                  <input
                    type='text'
                    placeholder='Title of the Book'
                    name='title'
                    className='form-control'
                    value={this.state.title}
                    onChange={this.onChange}
                  />
                </div>
                <br />

                <div className='form-group'>
                  <input
                    type='text'
                    placeholder='ISBN'
                    name='isbn'
                    className='form-control'
                    value={this.state.isbn}
                    onChange={this.onChange}
                  />
                </div>

                <div className='form-group'>
                  <input
                    type='text'
                    placeholder='Author'
                    name='author'
                    className='form-control'
                    value={this.state.author}
                    onChange={this.onChange}
                  />
                </div>

                <div className='form-group'>
                  <input
                    type='text'
                    placeholder='Describe this book'
                    name='description'
                    className='form-control'
                    value={this.state.description}
                    onChange={this.onChange}
                  />
                </div>

                <div className='form-group'>
                  <input
                    type='date'
                    placeholder='published_date'
                    name='published_date'
                    className='form-control'
                    value={this.state.published_date}
                    onChange={this.onChange}
                  />
                </div>
                <div className='form-group'>
                  <input
                    type='text'
                    placeholder='Publisher of this Book'
                    name='publisher'
                    className='form-control'
                    value={this.state.publisher}
                    onChange={this.onChange}
                  />
                </div>

                <input
                    type="submit"
                    className="btn btn-outline-warning btn-block mt-4"
                />
              </form>
          </div>
          </div>
        </div>
      </div>
    );
  }
}

export default CreateBook;

Run the project and visit http://localhost:3000/create-book. Now you will see a message like the following after creating a new book. You will also get an error message if the system fails to add a new book.

Books List Success Notification

You can apply this same method in different components in your project. Notifications will be displayed in different colors depending on the notification type: info, success, warning, and error.

You can also pass five different parameters along with the message: message, title, timeOut, callback, and priority.

Available NotificationManager APIs

For this package, there are four different APIs available to us of the following types:

  • info
  • success
  • warning
  • error

Here’s an example for the success type — simply replace success with the proper notification type for the given scenario:

NotificationManager.success(message, title, timeOut, callback, priority);

The parameters that follow the notification type are described below:

  • message: the message we want to pass. It has to be a string.
  • title: The title of the notification. Again, its type is string.
  • timeOut: The popup timeout in milliseconds. This has to be an interger.
  • callback: We can pass a function (type; function) through the notification. It executes after the popup is called.
  • priority: This is a boolean parameter. We can push any notification to the top at any point by setting the priority to true.

Configuring notifications with react-toastify

Now that we have discussed react-notifications, let’s move on to react-toastify. Both packages serve a similar purpose, but react-toastify has more built-in features than react-notifications, and it is also more open to customization.

Now on version 5.3.2, it is clear the react-toastify team has a good eye on maintenance. Additionally, react-toastify is almost 16 times more popular than react-notifications according to their weekly downloads record at the time of writing.

react-toastify was built with many features, some of which are:

  • Easy to integrate
  • Customizable
  • Allows users to close displayed notifications by swiping
  • A fancy progress bar to display the remaining time on the notification

For this part, I want to create a new project to show the whole setup. Let’s use create-react-app to get an initial setup for our React project.

$ npx create-react-app react-notification-example

From the project directory (react-notification-example), run the project:

$ npm start

Now, open the App.js file and update it with this:

import React from 'react';
import './App.css';


class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      total_file_size: 0,
      download_limit: 100
    };
  }

  add_to_download_card = size => {
    if(this.state.total_file_size + size <= this.state.download_limit) {
      this.setState({
        total_file_size: this.state.total_file_size + size
      });
    }
  };

  reset = e => {
    this.setState({
      total_file_size: 0
    });
  }


  render() {
    return (
      <div className="App">
          <header className="App-header">
            <div>
              <button className='inc' onClick={() => this.add_to_download_card(40)}>
                Download A(40GB)
              </button>

              <button className='inc' onClick={() => this.add_to_download_card(80)}>
                Download B(80GB)
              </button>

              <button className='inc' onClick={() => this.add_to_download_card(30)}>
                Download C(30GB)
              </button>
            </div>

            <div>
              <button className='reset' onClick={this.reset}>
                Reset
              </button>
            </div>

            <b>
              Download Limit: {this.state.download_limit} GB
            </b>

            <h1>
              Total File Size: {this.state.total_file_size} GB
            </h1>
          </header>
      </div>
    );
  }
};

export default App;

This update will change the view of your http://localhost:3000/, and you should see the following page on your browser:

Our App In A Web Browser

Here you have three options to download three different files by clicking them. Once you click any of them, Total File Size will display the updated number (total number of GB you have downloaded). We set the download limit to 100. You can change them, of course, and there is also a Reset button to reset the total download size.

NOTE: This app doesn’t have any server functionality, and those Download buttons won’t download any file. It’s just a counter that counts total downloaded file size. This is enough to show the uses of react-toastify.

react-toastify installation

From your project folder (react-notification-example), run the command for your preferred package manager to install react-toastify:

$ npm install --save react-toastify
$ yarn add react-toastify

Now, update App.js with these two lines to import the necessary stuff for react-toastify:

import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

After that, add ToastContainer inside the render function once in your application tree. If you are not sure where to put it, then rendering it in the application root would be the best option.

<ToastContainer position={toast.POSITION.TOP_RIGHT}/>

Position is optional here, but the default position value is the top right of your browser. If you want, you can replace the position value with any of the following:

  • TOP_LEFT
  • TOP_CENTER
  • TOP_RIGHT
  • BOTTOM_LEFT
  • BOTTOM_CENTER
  • BOTTOM_RIGHT

Now you can set notifications to pass through ToastContainer. I have added three different types of notifications — success, error, and info — inside the add_to_download_card and reset functions.

Our final App.js file should look like this:

import React from 'react';
import './App.css';

// React-Toastify
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      total_file_size: 0,
      download_limit: 100
    };
  }

  add_to_download_card = size => {
    if(this.state.total_file_size + size <= this.state.download_limit) {
      this.setState({
        total_file_size: this.state.total_file_size + size
      });
      toast.success("You have downloaded a "+ size + " GB file Successfully!");
    } else {
      // notification
      toast.error("Download Limit Exceeded!");
    }
  };

  reset = e => {
    this.setState({
      total_file_size: 0
    });
    toast.info("Download Counter is initialized with 0");
  }


  render() {
    return (
      <div className="App">
          <header className="App-header">
            <div>
              <button className='inc' onClick={() => this.add_to_download_card(40)}>
                <b>Download A(40GB)</b>
              </button>

              <button className='inc' onClick={() => this.add_to_download_card(80)}>
                <b>Download B(80GB)</b>
              </button>

              <button className='inc' onClick={() => this.add_to_download_card(30)}>
                <b>Download C(30GB)</b>
              </button>
            </div>

            <div>
              <button className='reset' onClick={this.reset}>
                <b>Reset</b>
              </button>
            </div>

            <b>
              Download Limit: {this.state.download_limit} GB
            </b>

            <h1>
              Total File Size: {this.state.total_file_size} GB
            </h1>
          </header>
          <ToastContainer position={toast.POSITION.TOP_RIGHT}/>
      </div>
    );
  }
};

export default App;

You will get the following success notification after every successful downloading attempt:

File Download Success Notification

If you look at the notification closely, you’ll see there is a progress bar within the notification. This indicates the remaining display time for the notification.

You get the following error notification when you try to execute a download after exceeding or meeting the download limit:

File Download Error Notification

And it will show an info notification when you press the Reset button:

File Download Info Notification

You can also dismiss any notification by simply clicking it, or you can swipe them to the left or right.

react-toastify is fully customizable, and there are also many more exciting features to fulfill all your needs. You can check out the full documentation for react-toastify here, and you can find the GitHub repo for the whole project here.

Conclusion

Today we have discussed adding a spinner and two different packages for managing notifications in a React project. Both notification packages are popular and customizable.

react-notifications is simpler than react-toastity, but I would recommend react-toastify over react-notifications because the former is more popular and has more customizable options to go along with all the same features of react-notifications.

Plug: , a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Nur Islam Sport Programmer

One Reply to “Adding spinners and notifications to your React app”

Leave a Reply