Making a simple notification system

January 01, 2024

I recently created a notification system in React and wanted to compose a light write-up here! I won't delve fully into the implementation, but I'll touch upon some notable aspects below to provide some soft guidance.

Firstly, what is a notification component?


A notification component usually appears subtly on a page to alert the user to some information; it then typically self closes after a short time interval.
The appearance of a notification could be a direct consequence of an action the user has performed (in which case the notification acts as a feedback mechanism), or it could be an informative message like informing the user that a background update is ready to be installed. Notifications alert the user of something but it is much less obtrusive to the user experience than a modal for instance.

Side note: I came across a toast component while reading up on notification components. An interpretation I came across to differentiate between the two is that the former does not require any sort of interaction from the user and serves more as an informative component. On the other hand, a notification component often requires the user to engage with it e.g. clicking on a button / CTA to do "something".


Thinking about the DX...


Designing the aesthetics of any component is always important, but what was interesting here was thinking about how to build a notification system that would feel "good" to use for developers.

As mentioned earlier, a common use case for a notification component is to act as a feedback mechanism. For example, we may want to show a success notification when a user adds something to their basket on a e-commerce site. Or, if we go the other way, we might want to show an error if something went wrong when trying to add the item to the basket.

With the above in mind, we'll need to way to create notifications on the fly - developers should be able to create a notification in their specific scenario and expect it to surface to the user.

After some iterations, I ended up with something that looks like this:

const notification = notificationManager.create('<NOTIFICATION_TYPE>', {
  heading: 'Hello world',
  message: 'Your first notification',
});



I felt that the interface of the .create parameter was quite neat. The first parameter takes in the type of notification you want to create, and the second parameter is the configuration object e.g. heading, message, timeout, action, etc..

The next scenario I wanted to cover was transitioning to a different type of notification. Examples I saw online simply pushed a new (and separate) notification to communicate the notification change. To illustrate this point, imagine a user has just fired off an asynchronous action; an initial notification would then be shown to the user; once the action completes, a new notification would be "stacked" on top of the initial one. There's nothing wrong with this approach, but I wanted to create a way in which the initial notification could be updated into a new one.

Having an update method on the notification instance seemed logical! So I ended up with something like the below:

notification.update({
  type: '<SOME_OTHER_TYPE>',
  heading: 'Notification updated',
  message: 'Action was a success!',
});



In its simplest form, I found that exposing these two methods would suffice for getting a small notification system to function. I did also have a close method on the notification manager but this was just to handle the self-closing functionality - I needed a way to "close" a notification when I didn't have the notification instance in the same context - and I did not expect the user to have to worry about this method at all.

Notification Manager


As you saw above, the first snippet contains a notificationManager. This was a custom utility hook I created that would allow developers to "instantiate" a notification manager to manage creating and updating notifications.

Essentially, the final code would look something like this:

const notificationManager = useNotificationManager();

const someClickHandler = async () => {
  const notification = notificationManager.create('info', {
    heading: 'Saving',
    message: `We're saving your work - hold tight...`,
  });

  // Fire off some async action
  await save();

  // After async action resolves, update notification
  notification.update({
    type: 'success',
    heading: 'Saved! ✅',
    message: 'We successfully saved your work',
  });
};



Defining a custom hook like this is a nice way to re-use the logic across your app!