Skip to main content
Using React Hot Toast as a Notification Provider for CRUD apps
16 min read

Using React Hot Toast as a Notification Provider for CRUD apps

This article was last updated on August 20, 2024, to add sections on Accessibility Considerations and Testing Toast Notifications.

Introduction

Building data-intensive enterprise web applications entails coalescing numerous features that have proven to be cumbersome to develop.

An effective notification system ensures proper feedback for changes throughout your application, effortlessly improving the user experience, which has become a hallmark for modern applications.

Javascript libraries like React simplify the difficulty of building data-intensive web applications with their innovative architecture and developer-friendly syntax in an ever-evolving ecosystem. There's only so much React can do to provide a foolproof solution for building these kinds of applications. This is where frameworks like Refine come into play.

Using a modular design, Refine leverages the best of what the React ecosystem has to offer, ranging from design systems to state management and notification libraries, to create a multipurpose React toolkit. This toolkit not only makes building enterprise-grade applications like admin panels, dashboards, and B2B applications a breeze but also provides built-in notification providers for centralized notification management.

In this article, we'll introduce Refine and explore how to set up a Refine application and create a custom notification provider using the react-hot-toast library.

Steps we'll cover:

What is react-hot-toast

react-hot-toast

React-hot-toast is a lightweight and open-source notification library for React. Like other React toast libraries, this library is designed to mimic push notifications popularized by native operating systems, such as iOS and Android, in web applications.

The library provides a simple API for creating and displaying various types of notifications, including success, error, and loading messages in React or React-based frameworks like refine. With minimal setup requirements, you can easily incorporate a toast notification into your application using a component-based approach facilitated by the <Toaster /> component and the toast() function.

The <Toaster /> component and the toast() function are the primary building blocks of the react-hot-toast library. The <Toaster/> component creates a DOM element for rendering toast notifications and accepts several props and options that can be used to customize the appearance and behavior of the notifications. Below is the basic structure of the <Toaster /> component:

<Toaster
position="top-center"
reverseOrder={false}
gutter={8}
containerClassName=""
containerStyle={{}}
toastOptions={{
// Define default options
className: "",
duration: 5000,
style: {
background: "#363636",
color: "#fff",
},
}}
/>

The toast() function triggers a notification anywhere in your application. It accepts the toast message and an optional configuration object, also known as ToastOptions, as its second argument.

This object is used to configure and customize the appearance and type of the toast notification. If a configuration object is provided, it will overwrite the toastOptions object property on the <Toaster/> component.

The following example demonstrates how to invoke a toast notification with similar toastoptions as the previous example with the <Toaster/> component.

toast("Hello World", {
className: "",
duration: 799,
style: {
background: "#363636",
color: "#fff",
},
});

Alternatively, the toast function offers the option to chain methods that invoke specific notification types, such as success, error, loading, and custom.

Success;
toast.success("Successfully created!");

Error;
toast.error("This is an error!");

Loading;
toast.loading("Waiting...");

Custom;
toast.custom(<div>Hello World</div>);

Refer to the react-hot-toast documentation to learn more about the toast function.

What is Refine

Refine is an open-source, React-based framework that facilitates the development of enterprise web applications, such as admin panels, dashboards, and B2B applications. It is a feature-rich library that prioritizes simplicity by eliminating repetitive tasks for CRUD, routing, internalization, and networking.

Refine is innately agnostic, which allows for the seamless integration of the highlighted features with widely used design systems, data fetching, state management, and complex form management libraries such as Material Design, Ant Design, React Query, React Hook Form, and more.

Additionally, Refine's loosely coupled design allows for the seamless integration of various backend architectures, ranging from a simple REST API to complex headless CMSs and databases, including:

  • Supabase
  • AppWrite
  • Strapi
  • Airtable
  • Hasura

The documentation provides detailed information about Refine and its architecture. You can start there to learn more about the Refine framework. However, in the section below, we will explore how to set up a Refine application from scratch.

Set up a Refine app

The Refine app Scaffolder is an efficient tool that allows you to create Refine app seamlessly in your browser.

You can choose the libraries and frameworks you want to work with, and the tool will generate a boilerplate code for you. Once you have completed these steps, you will be able to follow the on-screen instructions to create a Refine application tailored to your preference.

For this tutorial, we will use the following configurations:

  • React platform: Vite
  • UI framework: Headless
  • Backend: REST API
  • Authentication Provider: No Auth
  • Do you want examples pages: Yes

After creating your project, give it a title or use the default title, and then build and download the Gzipped compressed file to your local computer.

Next, extract the downloaded compressed file using a zip file extractor and open the project in an IDE of your choice. Finally, run the following commands to install the project dependencies and start the development server.

// Install dependencies
npm install

// Start development server
npm run dev

Upon successfully starting the development server, navigate to http://localhost:5173 on your browser of choice. Your project should render as shown in the image below.

react-hot-toast

What is Refine notification provider

The Refine notification provider is a feature that enables the display of notifications to users within a Refine application. The primary purpose of the notification provider is to centralize the management of notifications, making it easy to display different types of notifications, such as “success”, “error”, and “progress”, to users from different parts of the application.

To understand how the notification provider works, we first need to comprehend how it interacts with the application. For every design system you choose to use in your Refine application, whether it's Ant Design, Material Design, Mantine, or Chakra UI, Refine provides a built-in notification provider that is passed as a prop to the Refine component.

import { useNotificationProvider } from "@refinedev/antd";

return (
<Refine
//...
notificationProvider={useNotificationProvider}
/>
);

The code above uses Refine's built-in notification provider for Ant Design. When a notification is triggered, Refine will use the notification provider to invoke the Ant Design notification object and display notifications in the application. The same goes for the highlighted design systems.

react-hot-toast

The notification provider is an object with open and close methods, which Refine uses to show and hide notifications.

const notificationProvider = {
open: () => {},
close: () => {},
};

The **open** and **close** methods The open method is invoked when Refine needs to send a notification and provide feedback on users' actions in the application.

The method takes an object argument with several properties that are used to set the message, description, type, and a unique key for the notification. The predefined abstract type below illustrates the properties available to the open method.

interface OpenNotificationParams {
key?: string;
message: string;
type: "success" | "error" | "progress";
description?: string;
cancelMutation?: () => void;
undoableTimeout?: number;
}

The key, message, and type properties are the only required properties of the group; the rest are optional. The message and type properties accept the respective notification message and type from Refine, while the key property accepts a unique ID that helps Refine identify active notifications and prevents it from displaying duplicate notifications.

export const notificationProvider = {
open: ({ key, message, type, undoableTimeout, cancelMutation }) => {
...
},
};

Unlike the open method, the close method only takes a key argument, which contains the unique ID of the active notification.

export const notificationProvider = {
...
close: (key: any) => toast.dismiss(key),
};

The close method uses this unique ID to terminate or dismiss the toast notification when the set duration has elapsed.

The **useNotification** hook The useNotification hook is a special function that allows you to manually invoke notifications anywhere in a Refine application. It exports the open and close methods from the notification provider, which allows you to show and hide notifications from any component.

const { open, close } = useNotification();

// Open notification
open({
type: "success",
message: "Success",
description: "This is a success message",
});

// close notification
close("notification-key");

To learn more about the useNottification hook, please refer to the documentation page.

We'll see the open and close methods in action in the next section, as we explore how to create a custom notification provider Using the react-hot-toast library.

Create custom notification provider with react-hot-toast

The notification provider is not included in your Refine application by default, depending on your project's configuration. It is only set up in projects configured to use a select design system. Therefore, headless projects without a design system, such as the project we created earlier, use Refine's default notification provider, which does not add notification functionality to your application.

However, Refine provides the option to create custom notification providers with the React toast library of your choice. Using the notification provider object as a single source of truth, you can create a notification provider with any toast library in the React ecosystem. In this section, we will demonstrate how you can create one with the react-hot-toast library.

Install react-hot-toast

As a first step, open your IDE's command-line tool or use the built-in one on your machine, and cd to your project's directory. Next, run the following command to install the react-hot-toast library as a dependency for your Refine application:

npm install react-hot-toast

Set up a notification provider

To set up a custom notification provider, we'll begin by creating a dedicated file to house all of our notification provider's logic.

Inside the src directory, create a new folder and name it providers. You can choose any name you prefer, but it's recommended to use providers to maintain a coherent file structure.

Next, create a new file inside the newly created providers directory and name it NotificationProvider.tsx. Then, add the following code:

import React from "react";
import { NotificationProvider } from "@refinedev/core";

export const notificationProvider: NotificationProvider = {
open: ({ key, message, type }) => {},
close: (key: any) => {},
};

In the code above, we export a notificationProvider object with the open and close methods defined. Then we use the built-in NotificationProvider method type from Refine's core to validate the object's structure and add type safety to the methods.

This is what the NotificationProvider object type looks like under the hood:

interface NotificationProvider {
open: (params: OpenNotificationParams) => void;
close: (key: string) => void;
}

We have created a base for our notification provider. Next, we will examine how to integrate the react-hot-toast library into the provider.

Integrating react-hot-toast

As previously explained, the toast function invokes a toast notification when called. Therefore, we can declare it inside the open and close methods and pass it the necessary arguments from each methods.

This way, when Refine calls the open method, the toast method will be triggered, and a toast will be displayed on the screen. Similarly, when the notification time elapses, the close method is called, and the notification is removed.

Now, return to your code and update it with the following:

import React from "react";
import { NotificationProvider } from "@refinedev/core";
import toast from "react-hot-toast";

export const notificationProvider: NotificationProvider = {
open: ({ key, message, type }) => {
switch (type) {
case "success":
toast.success(message, {
id: key,
position: "top-right",
});
break;
case "error":
toast.error(message, {
id: key,
position: "top-right",
});
default:
break;
}
},

close: (key: any) => {
toast.dismiss(key);
},
};

Here, we import the toast function from react-hot-toast and declare it inside the open and close methods, with the message and key arguments passed to it.

Since the toast function's toast options don't include a type property that we can pass the type argument from the open method to, we use a switch statement to check the type of notification being invoked by Refine and chain the appropriate method to the toast function.

In the close method, however, we chain a dismiss method to the toast function and pass it the key argument. This method uses the key to identify active toast notifications and terminates them when the close method is called.

After completing the steps above, your NotificationProvider.tsx file should look as follows:

import React from "react";
import { NotificationProvider } from "@refinedev/core";
import toast from "react-hot-toast";

export const notificationProvider: NotificationProvider = {
open: ({ key, message, type }) => {
switch (type) {
case "success":
toast.success(message, {
id: key,
position: "top-right",
});
break;
case "error":
toast.error(message, {
id: key,
position: "top-right",
});
default:
break;
}
},
close: (key: any) => {
toast.dismiss(key);
},
};

To complete the setup, we'll add the newly created notificationProvider object and the <Toaster/> component from react-hot-toast to the Refine context via the notificationProvider prop and the route element on the <Refine> component.

To do this, open the App.tsx file and make the following modifications:

...
import { Toaster } from "react-hot-toast";
import { notificationProvider } from "./providers/notificationProvider";


function App() {
...
return (
<BrowserRouter>
<Refine
...
notificationProvider={useNotificationProvider}
i18nProvider={i18nProvider}
...
>
<Routes>
<Route
element={
<Layout>
<Outlet />
<Toaster />
</Layout>
}
>
...
</Route>
</Routes>
<UnsavedChangesNotifier />
</Refine>
</BrowserRouter>
);
}

Congratulations! You have successfully created a custom notification provider for your Refine application. You can now preview your notification by editing or creating a record on the dashboard.

Success notification

react-hot-toast

Positioning your toast notifications

React Hot Toast lets you easily pick where you want the toasts to show up on the screen. It has six ready-to-use spots, but you can also set your own spot using the 'position' setting.

toast.success("Successfully logged in", {
position: "top-left",
style: {
background: "green",
color: "#fff",
},
});

toast.success("Successfully logged in", {
position: "top-center",
style: {
background: "green",
color: "#fff",
},
});

toast.success("Successfully logged in", {
position: "top-right",
style: {
background: "green",
color: "#fff",
},
});

toast.success("Successfully logged in", {
position: "bottom-left",
style: {
background: "green",
color: "#fff",
},
});

toast.success("Successfully logged in", {
position: "bottom-center",
style: {
background: "green",
color: "#fff",
},
});

toast.success("Successfully logged in", {
position: "bottom-right",
style: {
background: "green",
color: "#fff",
},
});
react-hot-toast

Accessibility Considerations

I wanted to bring forward some accessibility considerations about our toast notifications so they are easy to be used by everyone. Here are a few key points, with examples:

Screen Reader Support

We need to make sure that our notifications are accessible via screen readers. We can use ARIA roles and live regions to announce notifications.

import { toast } from "react-hot-toast";

toast.success("Operation successful!", {
ariaProps: {
role: "alert",
"aria-live": "assertive",
},
});

Keyboard Navigation

The users should be able to interact with notifications via their keyboards. In fact, we can provide a control within the notification, which is focusable, that when activated using the Enter key can be dismissed.

toast((t) => (
<div>
Operation successful!
<button onClick={() => toast.dismiss(t.id)} aria-label="Dismiss">
Dismiss
</button>
</div>
));

Contrast and Visibility

We have to be sure that the text in the notifications has a good contrast with the background for easy readability. We can do this by using high contrast ratio colors.

toast.success("Success!", {
style: {
background: "#333",
color: "#fff",
},
});

Non-Intrusive Design

Notifications should be simple and not get in the way of what the user is doing. We can even make it possible for the user to get rid of notifications or to turn off notifications in their entirety.

toast("This is a non-intrusive notification.", {
duration: 4000,
position: "bottom-right",
});

Dismissing React Hot Toast Notifications

We can make toast notifications go away by setting a time or using the 'dismiss' button. To use this button, you need the toast's special ID or the toast itself. You usually get the toast when you show the notification. To get rid of one toast, use the 'dismiss' function with its ID. To get rid of all toasts, just use the function without any ID.

Testing React Hot Toast Notifications

It's very important that toast notifications work across many scenarios. So, my simple approach toward testing toast notifications in a React application would be:

Check Display

Confirm that the toast notification is displayed upon trigger. This is generally concerned with ensuring that, when triggered, the screen can display the toast message.

const toastMessage = "This is a test toast";

toast(toastMessage);
expect(screen.getByText(toastMessage)).toBeInTheDocument();

Test Different Types

Verify different types of toast messages (for example, success, error) to be of the correct style and message.

toast.success("Success message");
expect(screen.getByText("Success message")).toHaveClass("toast-success");

toast.error("Error message");
expect(screen.getByText("Error message")).toHaveClass("toast-error");

Positioning

Place the toast at the correct position on the screen (e.g., top-right, bottom-center).

toast("Position test", { position: "top-right" });

expect(screen.getByText("Position test")).toHaveStyle({
position: "absolute",
top: "0",
right: "0",
});

Auto Dismiss

Make sure that the toast vanishes after a given period of time if set as auto-dismiss.

jest.useFakeTimers();

const toastId = toast("Auto-dismiss test", { duration: 3000 });
expect(toast.isActive(toastId)).toBeTruthy();

jest.advanceTimersByTime(3000);
expect(toast.isActive(toastId)).toBeFalsy();

Manual Dismissal

Verify that the toast can be manually dismissed by a user or programmatically.

const toastId = toast("Dismiss test");

toast.dismiss(toastId);
expect(screen.queryByText("Dismiss test")).toBeNull();

Conclusion

As demonstrated in the article, Refine abstracts the overhead complexity involved in building system-wide customizable notification systems in React applications. Given its robust functionalities and the highly customizable nature of the react-hot-toast library, you can improve on what’s covered in this tutorial, or better yet, use any of the design systems with their built-in notification systems.

Live CodeSandbox Example

Run on your local
npm create refine-app@latest -- --example blog-react-hot-toast