Creating a global notifications system with superstate and React
Tags: superstate, react, notifications, javascript
Published: 2022-07-20 19:11:49

---
title: Creating a global notifications system with superstate and React
published: true
description: Make your application dispatch notifications with a super small amount lines of code!
tags: superstate, react, notifications, javascript
---

Greetings, developers!

I'd like to show you my take on how to build a simple global notifications system with [superstate](https://superstate.dev) and [React](https://reactjs.org).

We have one extra, implicit goal: building something with satisfying ergonomics and developer experience.

With no further ado,

---

<center>_If you prefer, there's also a video of this guide available!_</center><br/>

{% embed https://www.youtube.com/watch?v=lU6QkD0gGYg %}

---

## Prerequisites

I'm going to create a brand new `create-react-app` application with TypeScript:

```shell
yarn create react-app superstate-notifications --template typescript
```

<small><em>Note that I am using `yarn`, but you can mimic my commands using `npm` as well.</em></small>

Once it's done, let's move our working directory to the `superstate-notifications` application we just created:

```
cd superstate-notifications
```

And then let's install **superstate**:

```
yarn add @superstate/core
```

Cool. Now we have a project that's good to go.

## What is superstate?

![superstate hero image](https://camo.githubusercontent.com/a322c815776e3781f58c3525d95c7e88b8bc932c2b3b21a15bc9727540274101/68747470733a2f2f692e696d6775722e636f6d2f456865635637472e706e67)

In short, **superstate** is a micro state management library for JavaScript applications. Despite the nuances, you can think of it as an alternative solution for [Redux](https://redux.js.org/) or [Zustand](https://github.com/pmndrs/zustand).

It was designed with developer wellness in mind, and comes bundled with a powerful and handy drafts system to make our lives easier and less repetitive.

## Getting started

Now that you have a working project to get your hands dirty, let's create a `notifications.tsx` file within `src/` and bootstrap the state of our notifications:

```tsx
import { superstate } from '@superstate/core'

const notifications = superstate([])
```

Note the `[]` within `superstate()`. That's the initial value of your state. It's as if you'd have typed:

```tsx
const notifications = []
```

Except that you wrapped the empty array inside a superstate, and that gives us powers.

## Creating & destroying notifications

The next step is creating the two most important functions of the notifications feature: `notify` and `destroy`. Respectively, one is meant to issue new notifications and the other is to destroy them.

This is what I came up with:

```tsx
function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}
```

### The `notify` function

It expects to receive a `message` (of type `string`) argument. This message is what the user is going to see once the notification pops up.

Also, this function declares an `id` variable and assigns `Math.random().toString()` to it. This is just because we want our system to support multiple notifications at once, and we must have a way to differentiate one notification from another&mdash;`id` is the way.

Furthermore, the `notify` function calls `.set()` from our `notifications` object. If you scroll up a little, you're going to notice this `notifications` object is our `superstate()` variable, thus `.set()` is a function returned from it.

It may look complicated at first, but all we're doing is passing to `.set()` a function that returns what the list of notifications should look like once we emit this new one.

`prev` is the previous value of `notifications`. Initially, the value of `notifications` is `[]` (an empty array), but as we start emitting notifications, this array will eventually grow&mdash;so `prev` ensures that we're _adding_ new notifications instead of replacing them.

Look at what we are doing again:

```tsx
notifications.set((prev) => [...prev, { id, message }])
```

It means the next value of `notifications` is the former notifications plus the new one, which is represented by an object with the `id` and `message` properties.

### The `destroy` function

Here we are telling that the next value of `notifications` is all notifications but the one that matches the specified `id` passed through the argument of the `destroy` function:

```tsx
notifications.set((prev) => prev.filter((p) => p.id !== id))
```

## Rendering notifications

Now in this same `notifications.tsx` file, let's create a Notifications Renderer. Its job is critical: displaying the notifications to the user.

Here's the bootstrap of it:

```tsx
export function NotificationsRenderer() {
  useSuperState(notifications)

  return null
}
```

**_Wait, what? Where is this `useSuperState()` function coming from?_**

Yeah, I did not mention it so far. Intentionally. In order to integrate superstate with React, you have to install an extra dependency:

```
yarn add @superstate/react
```

And import it in your `notifications.tsx` file:

```
import { useSuperState } from '@superstate/react'
```

The `useSuperState` hook re-renders our component (NotificationsRenderer) every time the state passed to it changes. In our context, this "state passed to it" refers to `notifications`.

This is what I came up with to make the renderer fully functional:

```tsx
export function NotificationsRenderer() {
  useSuperState(notifications)

  if (!notifications.now().length) {
    return null
  }

  return (
    <div>
      {notifications.now().map((n) => {
        return (
          <div key={n.id}>
            <p>{n.message}</p>

            <button onClick={() => destroy(n.id)}>
              Destroy
            </button>
          </div>
        )
      })}
    </div>
  )
}
```

Let's break it down:

```tsx
if (!notifications.now().length) {
  return null
}
```

The `if` above guarantees that nothing will be rendered when no notifications exist. Note the `now()` method&mdash;it returns the _current_ value of your `notifications` array. The condition states that if there are no items in the `notifications` list, then we'd like to render `null`.

```tsx
{notifications.now().map((n) => {
```

The line above will iterate over each item in the `notifications` array and return something. In our context, for each notification, something will be rendered. _Note that `now()` is present again._

```tsx
return (
  <div key={n.id}>
    <p>{n.message}</p>

    <button onClick={() => destroy(n.id)}>
      Destroy
    </button>
  </div>
)
```

The lines above refer to the actual notification item that will be rendered in the browser and displayed to the user.

As of the last piece of the rendering puzzle, let's open `./src/App.tsx` and clear the returned component to look something like this:

```tsx
export default function App() {
  return ()
}
```

With the house clean, we can now render our renderer:

```tsx
import { NotificationsRenderer } from './notifications'

export default function App() {
  return (
    <div>
      <NotificationsRenderer />

      <button>Give me a notification!</button>
    </div>
  )
}
```

## Emitting notifications

You may have noticed we created a `Give me a notification!` button at the above post section but have done nothing with it. Well, yet.

Let's make it give us a notification whenever it is clicked:

```tsx
<button onClick={() => notify('Hello world!')}>
  Give me a notification!
</button>
```

The `notify` function won't work right away. We first have to export it. Go back to `notifications.tsx` and export both `notify` and `destroy` functions by prepending the `export` keyword in front of the `function` keyword:

```tsx
export function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

export function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}
```

Now, at `App.tsx`, you'll be able to import them:

```tsx
import {
  notify,
  destroy,
  NotificationsRenderer,
} from './notifications'
```

And boom! Save all your files and go to your browser to play with your fresh notifications system. :)

## Wrapping up

Your final `notifications.tsx` should look like this:

```tsx
import { superstate } from '@superstate/core'
import { useSuperState } from '@superstate/react'

const notifications = superstate([])

export function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

export function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}

export function NotificationsRenderer() {
  useSuperState(notifications)

  if (!notifications.now().length) {
    return null
  }

  return (
    <div>
      {notifications.now().map((n) => {
        return (
          <div key={n.id}>
            <p>{n.message}</p>

            <button onClick={() => destroy(n.id)}>
              Destroy
            </button>
          </div>
        )
      })}
    </div>
  )
}
```

And your `App.tsx`:

```tsx
import {
  notify,
  destroy,
  NotificationsRenderer,
} from './notifications'

export default function App() {
  return (
    <div>
      <NotificationsRenderer />

      <button onClick={() => notify('Hello world!')}>
        Give me a notification!
      </button>
    </div>
  )
}
```

You can see a slightly fancier example on [StackBlitz](https://stackblitz.com/edit/superstate-notifications-system?file=src/app.tsx):

{% embed https://stackblitz.com/edit/superstate-notifications-system?embed=1&file=src/app.tsx %}

## Final thoughts

This is a pretty basic notifications system, but quite powerful and intuitive. Now, to dispatch notifications in your app, all you have to do is calling the `notify()` function you created yourself from anywhere in your app, including non-React code, and have fun because things will just work.

Now you go have some fun and don't hesitate to reach out with any questions or feedback! d(^_^)z