Written by Nur Islam✏️
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?
- We will review all the necessary steps needed to add a spinner (to indicate loading, for example) to our project.
- 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, replacespinner.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: []
};
}
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
.
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.
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:
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:
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:
And it will show an info
notification when you press the Reset button:
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.
Editor's note: Seeing something wrong with this post? You can find the correct version here.
Plug: LogRocket, 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.
Try it for free.
The post Adding spinners and notifications to your React app appeared first on LogRocket Blog.
Top comments (1)
@bnevilleoneill I would love to see a post like this for Vue apps...I'd read it!