Create a robust notification system for your React App using MUI and Redux Toolkit – In Less Than 100 Lines Of Code

Using Redux toolkit, MUI, and React hooks, you can write a flexible and reliable notification system for any size app in less code than you thought possible.

A common request from many clients for their React apps is the ability to display brief notifications to call the user’s attention.

A great way to do this is to use a modern Redux framework, like Redux Toolkit.

Some examples include:

  • “SUCCESS: Your file has been saved!”
    • – Triggered when a process for saving files has just succeeded.
  • “ERROR: Your message failed to send”
    • – Triggered when they were attempting to send some sort of message across an api and that call resulted in an error.
  • “WARNING: That thing you wanted to happen only sort of happened!”
    • – Triggered when the results of a process are partially successful and partially not.
  • “WELCOME BACK”
    • – Triggered when a user opens the app again
  • “QUEUE: Your item has been placed in the queue”
    • – Triggered when some long process starts

These quick notifications are distinct from Modals and Dialogs in that they aren’t meant to completely take the user’s focus. Neither should they be very interactive except for maybe an (X) to close them. They are meant to be short, quick to read, only semi-intrusive, and most are meant to timeout and disappear after a few seconds.

Along with each of these example messages, each notification will probably include a distinct look and style to emphasize that message’s status. For example, error notifications could be red with a scary icon. A successful notification could be green with a check, or maybe some celebratory balloons if your app is cool like that.

How notifications are typically implemented

Assuming the dev is already working with a styling library–let’s say MUI–they might approach this by creating a simple custom Snackbar for each message and placing each of them in their respective components close to where the logic related to the notification resides. This is a straightforward and modular way to get notifications in your App.

Problems with a typical notification implementation

 

  • Copypasta: The larger the app, the more possible notifications there could be. And many of these notifications will probably be for sets of logic that have little to do with each other. You could end up adding the same, very similar piece of notification code in dozens of components in your app.
  • Inconsistent UX: When you have the same bit of code copied throughout the app it can create inconsistencies in implementation which can result in inconsistencies in the user experience.

There must be a better way!

Luckily, there is!

Using Redux Toolkit (RTK) and react hooks–along with our styling library of choice, which in this case we’ll continue with MUI–we can create one component that reads notification information from the redux store and displays or clears based on it.

Here’s a diagram which summarizes the code we’ll be writing along with the function of each file:

Notification System in 100 lines of code

Let’s start by installing the needed packages into our React app.

For our styling library we’ll use MUI:
> npm install @mui/material @emotion/react @emotion/styled

For our redux store we’ll use Redux Toolkit (RTK)
> npm install @reduxjs/toolkit react-redux

That’s it! Let’s begin writing code by creating our slice.

NOTE: A slice is a concept introduced by RTK that greatly simplifies creating reducers, creating actions, and calling those actions.

notificationSlice.ts

import { AlertColor } from "@mui/material";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export interface NotificationState {
  open?: boolean;
  type?: AlertColor;
  message?: string;
  timeout?: number | null;
}

export const notificationInitialState: NotificationState = {
  open: false,
  type: "info",
  message: "",
  timeout: 5000
};

export const NotificationSlice = createSlice({
  name: "notification",
  initialState: notificationInitialState,
  reducers: {
    addNotification: (_state, action: PayloadAction<NotificationState>) => ({
      ...notificationInitialState,
      ...action.payload,
      open: true
    }),
    clearNotification: (state) => ({ ...state, open: false })
  }
});

export const NotificationActions = NotificationSlice.actions;
export const NotificationReducer = NotificationSlice.reducer;

notificationSlice.ts file breakdown:

    1 – We begin by declaring what our slice of state will look like with the interface NotificationState. This is the data our eventual notification component will read from the store.

    2 – Next we’re stating that the default data for our notification state will be not open, of type info, with no message, and have a timeout of 5000 milliseconds.

    3 – Then we create the actual slice with the important property here being the reducers property.

      • We’re adding 2 methods to reducers: addNotification and clearNotification. Add notification will take data and update the store with that data, along with telling the hardcoded value of true for the open property. Calling addNotification implies that you want to see a notification now.
      • clearNotification is simpler: it simply sends data to the store to close the notification immediately.

    4 – Finally, we export the actions and reducer. The actions will be called in our hook while the reducer will be integrated into the redux store.

Line Counter: 33

Speaking of our redux store, let’s make it now:

store.ts

import { configureStore } from "@reduxjs/toolkit";
import { NotificationReducer } from "./notificationSlice";

export const store = configureStore({
  reducer: {
    notification: NotificationReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;

store.ts file breakdown:

  • We declare our state by using the configureStore method from RTK and we add one piece of state to it: notification.
  • Note that the import statement on line 2 assumes that notificationSlice.ts is in the same directory as store.ts. This isn’t necessarily a best practice as you may want your slice(s) and store to exist in separate directories or modules.

Line Counter: 44

Now that we have a store with a notification state and we have a slice with methods to update that state, we need to make a custom hook that dispatches those actions.

useNotification.ts

import { useDispatch } from "react-redux";
import { NotificationActions, NotificationState } from "./notificationSlice";

export const useNotification = () => {
  const dispatch = useDispatch();

  const displayNotification = (notification: NotificationState) => {
    dispatch(NotificationActions.addNotification(notification));
  };

  const clearNotification = () => {
    dispatch(NotificationActions.clearNotification());
  };

  return { displayNotification, clearNotification } as const;
};

useNotification.ts file breakdown:

  • This hook is simply a wrapper for useDispatch and the NotificationActions.
  • While this hook isn’t exactly needed, using it in this way prevents a dev from having to import useDispatch and NotificationActions every time they want to call a notification reducer.

Line Counter: 61

So we have the state in the store, we have the slice which updates the state, and we have the hook that wraps the slice reducers to make using it easier.

Next we need to create the actual notification component which will get its data and status from the store.

notification.tsx

import { Snackbar, Alert, SnackbarCloseReason } from "@mui/material";
import React from "react";
import { useSelector } from "react-redux";
import { RootState } from "./store";
import { useNotification } from "./useNotification";

export const Notification = (): JSX.Element => {
  const notification = useSelector((state: RootState) => state.notification);
  const { clearNotification } = useNotification();

  const handleClose = (_: unknown, reason?: SnackbarCloseReason) =>
    reason !== "clickaway" && clearNotification();

  return (
    <Snackbar
      open={notification.open}
      autoHideDuration={notification.timeout}
      onClose={handleClose}
    >
      <Alert
        variant="filled"
        onClose={handleClose}
        severity={notification.type}
      >
        {notification.message}
      </Alert>
    </Snackbar>
  );
};

notification.tsx file breakdown:

  • In this example we’re using the Snackbar and Alert components from MUI.
  • The component begins by selecting the notification state from the store. We then use the notification properties (open, message, timeout, and type) to control the components. As the state updates, the components will update as well.
  • When the notification closes (either by timeout or by the user manually closing it) it will call handleClose which calls our custom hook’s clearNotification method which updates the state.

Line Counter: 91

Finally, we simply wrap our main application component in the redux Provider component, inject our store into the provider, and add our notification component as a sibling to whatever our main component is:

index.tsx

...
import { Provider } from "react-redux";
import { store } from "./store";
import { Notification } from "./notification";

ReactDOM.createRoot(document.querySelector("#root")).render(
...
      <Provider store={store}>
        <Main />
        <Notification />
      </Provider>
...
);

index.tsx file breakdown:

  • Note that this is the only place in the entire app where we’ll need to add !

Final Line Counter: 97

And that’s it! We’ve just built a system that will display notifications anywhere in our app where we call displayNotification from our custom hook.

Now if we want to display a notification from anywhere, we simply include our custom hook at the top of our component and call displayNotification with our desired data, like this:

const MyComponent = () => {
    const { displayNotification } = useNotification();

    return (
      <Button
        onClick={() =>
          displayNotification({ message: "This is the default notification" })
        }
      >
        Click me to show a notification!
      </Button>
    )
}

That’s It!

Detailed Video Of Process

Video: Notification System In React With Less Than 100 Lines Of Code Using MUI (Material UI) and Redux Toolkit.

Conclusion

To see these notifications in action you can take a look at this codesandbox:

Code Sandbox: https://codesandbox.io/s/admiral-snackbar-3lchxu

About Intertech

Intertech is a Software Development Consulting Firm that provides single and multiple turnkey software development teams, available on your schedule and configured to achieve success as defined by your requirements independently or in co-development with your team. Intertech teams combine proven full-stack, DevOps, Agile-experienced lead consultants with Delivery Management, User Experience, Software Development, and QA experts in Business Process Automation (BPA), Microservices, Client- and Server-Side Web Frameworks of multiple technologies, Custom Portal and Dashboard development, Cloud Integration and Migration (Azure and AWS), and so much more. Each Intertech employee leads with the soft skills necessary to explain complex concepts to stakeholders and team members alike and makes your business more efficient, your data more valuable, and your team better. In addition, Intertech is a trusted partner of more than 4000 satisfied customers and has a 99.70% “would recommend” rating.