Super easy: Custom Toast message manager with React (and TypeScript)

Toast messages/app notifications manager in less than 5 minutes

Image for post
Image for post
Toast manager demo

What are we trying to build and why?

  1. A simple toast messaging system — many guides on the Internet tend to overcomplicate and overengineer things, so let’s keep it simple (KISS principle). We only need 2 files with 50–60 lines of code combined.
  2. A fully custom toast messaging system —maybe we picked a UI library that does not have a toast message component, or maybe it does have but it does not serve our needs, and now maybe we do not want to introduce a new UI library within our project to simply make use of a single component, or maybe we do not want to use a third-party UI library at all, so let’s create it ourselves from scratch.
  3. Display a toast message from anywhere—similar implementations tend to force us to include toast components as part of our JSX, or to wrap our app in something called “toast provider”, or to create a dedicated container in our app tree where we render toasts through Redux. Let’s build our toast manager with the ability to display toasts from anywhere through a function call without restrictions.

The base case requirements

Let’s approach this with a user story:

What does that mean?

  1. We need a container positioned at the bottom-right corner to hold multiple toast messages
  2. Each toast message should self-destroy in 3 seconds unless we manually close it earlier

The implementation

Step 1: Create a starting project

Navigate to your projects folder and create a new project there:

npx create-react-app toast-manager --template typescript
cd toast-manager
npm start

You should be able to open and see the project at localhost:3000

Step 2: Project structure

Create a components/ folder within src/. We only need two files there:

  • Toast.tsx — UI component for a single toast message
  • ToastManager.tsx — a class to manage toasts and render them on the screen

The project structure should be the following:

Image for post
Image for post
toast-manager folder structure

Step 3: Build Toast.tsx

Toast.tsx is a component that expects 5 props to be passed (interface ToastProps).

  1. title and content — the text message we want the user to see
  2. duration? —optional, creates an expiration timer for the message, not passing it means that the message must be closed manually
  3. destroy() — function to remove the toast from UI
  4. id — in ToastManager.tsx the id is passed as a parameter to destroy(), but here I am using it on line 27 to demo purposes

Step 4: Build ToastManager.tsx

ToastManager.tsx is a class that holds an array of toast messages and has the ability to create new toast messages and destroy existing ones.

We need to implement 3 methods, besides the constructor(), of course:

  • constructor() — creates a container as a child of <body> (outside of our app root)
  • show(options) —creates a toast object with a unique id and pushes it to the array, this is the method we use to display a message from anywhere within our app; the options parameter contains the props of a Toast component (id, title, content, duration)
  • destroy(id) — remove a toast message with a given id from the array
  • render() — this method gets called whenever we show() or destroy() a toast

We can now use toast.show(…options) to display toasts from anywhere!

[Bonus functionality]: Destroy a particular toast message from anywhere

Even though we won’t use it in the tutorial, keep in mind that we do have the functionality to create a toast with a custom id and then manually destroy it:

toast.show({  
id: 'my-id',
title: "toast title",
content: "toast body",
duration: 10000
});
toast.destroy('my-id');

Step 5: Include CSS

The essential part here is positioning the container created within the constructor of ToastManager.tsx. I am giving you some basic styles but you are free to include your own.

Go to src/index.css and paste the styles there:

Step 7: Display a toast message from anywhere

Go the src/App.tsx, import the toast manager instance, and create a button that calls toast.show(options) on click.

The result

Image for post
Image for post

[Optional] Step 8: Prevent unnecessary re-renders

It is highly unlikely that this would ever cause performance issues but in practice every time we show or destroy a message, all active messages are re-rendered. We can prevent unnecessary re-renders with React.memo().

Without memoization: I added a console.log() in Toast.tsx and then clicked the “show toast” button 8 times. The result is nearly 100 re-renders.

Image for post
Image for post
re-renders without memoization

Include memoization:

Open Toast.tsx and change line 35 from:

export default Toast;

To:

const shouldRerender = (prevProps: ToastProps, nextProps: ToastProps) => {
return prevProps.id === nextProps.id;
};
export default React.memo(Toast, shouldRerender);

The result with 8 button clicks (the very first console.log comes from a toast at App.tsx:7):

Image for post
Image for post
re-renders with memoization

What can be further improved?

  1. Toast message variants — currently we only display toasts with fixed styles but we can also style toasts as per context (success, error, warning, neutral, etc.).
  2. Multiple toast container placements — our implementation can only display toasts at the bottom-right corner of the screen but we can also include more containers (top-right, top-center, top-left, bottom-left, bottom-center)
  3. The content of the message — we currently expect the content of the message to be a string. Ideally, however, we should also be able to pass it as JSX (e.g. toast body with a confirmation button or a retry button in case of an API error)
  4. toast.destroyAll() — we currently use toast.destroy(id) to remove a single toast from UI and we can easily create a function that sets the toast array to initial value (empty)
  5. Animations — we have a basic intro animation (which can certainly be improved) and it would be even better if we had an exit animation as well
  6. Implement ToastManager.tsx as a singleton — meaning that we could only ever have a single instance of it

Written by

Frontend Developer, Sofia, Bulgaria, MComp Computer Science, MSci Business Management, Innovation & Technology

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store