In this blog post, we'll explore how to power real-time toast notifications in Next.js using Knock's feed API. Toasts aren't a new concept for most developers, but with Knock's API we can display toasts when actions are triggered by other users or external events. We'll cover installing toasts from shadcn/ui, connecting them to Knock's real-time feed events, and showing how you can conditionally display toasts in the UI based on your message payload.

This tutorial builds on a previous guide where we implemented a Notion-style feed, so be sure to check that out.

Video walkthrough

If you prefer to learn with video, watch it here.

Setting up the project

We'll start off by picking up where we left off in the previous tutorial. If you haven't read that post, check out the link above in the post intro.

The first thing we need to do to implement real-time toasts is install the toast component from shadcn/ui. Open up your browser and navigate to the toast component page in the shadcn/ui docs.

Then, copy the NPM command to add the component to your components/ui folder and run the command in your terminal.

npx shadcn-ui@latest add toast

You should see that it's installed three separate files: toast.tsx, toaster.tsx, and use-toast.tsx.

Importing the Toaster component

Next, we need to import the Toaster component into our ActivityFeed component file. We'll do that right under where we're importing the rest of our components:

import { Toaster } from "@/components/ui/toaster";

Then, scroll down to the bottom of the file, since everything is in one file for this project, and implement the Toaster component at the bottom of our Tabs component:

<Toaster />

Depending on what you're building, you may want to implement this at a higher level in your component tree, like a layout.tsx file.

Importing the useToast hook

We also need to import the useToast hook. The useToast hook allows us to trigger our toast notifications from other components. We can just copy the import statement from the docs:

import { useToast } from "@/components/ui/use-toast";

And paste it below where you're importing the rest of your React hooks.

Extracting the toast function

Inside our component, we need to extract the toast function from our useToast hook:

const { toast } = useToast();

This will allow us to call the toast function and pass in an object to trigger our toasts, as shown in the implementation guide.

Adding an Event Handler

Now that we have our toasts installed and the useToast hook in place, let's add an additional event handler onto the Knock feed that will respond to real-time events.

knockFeed.on("items.received.realtime", (response) => {
  const { items } = response;
  console.log(items);
  toast({ title: "New toast message", description: "You have a new message." });
});

Here, we're listening for the items.received.realtime event. The callback function will receive a response object, from which we destructure the items property. These are the new items being passed to the feed through WebSockets as we add items to the feed in the backend.

For now, we'll just console.log the items so we can take a look at them, and call the toast function with some generic data to make sure it's working.

Testing the Toast

Let's save our changes, start up the development server using npm run dev, and refresh the app in the browser to load the newest client-side code.

Then, head over to the Knock dashboard and run a test to send a message inside your in-app workflow.

Sending and receiving a real-time toast

In your app, you should see your toast displaying exactly as intended. And if you open up the console, you'll see the items array being logged out.

It's important to note that items is an array, because it's possible for multiple items to get batched together. We could potentially receive multiple items at once.

Handling multiple items

Let's modify our code to handle the case where we receive multiple items. Since items is an array, we'll use .forEach() to loop through it:

items.forEach((item) => {
  toast({ title: "New item in feed", description: item.data.message });
});

Now, instead of displaying just one toast, we're displaying one for each real-time item we receive. We're also using the message property from the data object on each item as the description for our toast.

Syncing the feed

Another thing we want to do inside our event listener is ensure that we're syncing our feed appropriately, so that when we get real-time events, the feed updates as well.

setFeed(knockFeed.getState());

We'll use the setFeed function and to reconcile our local feed state with the new state of the knockFeed object. The knockFeed does a great job of reconciling its own internal state each time we recieve new items or update statues on exisitng items, so we can just lean into that.

Customizing the toast message

There's a ton of data available on each item object that we can use to customize our toast messages. For example, let's use the inserted_at timestamp to display a friendly date:

toast({
  title: "New item in feed",
  description: `New item in feed at ${new Date(
    item.inserted_at,
  ).toLocaleString()}`,
});

Here, we're creating a new Date object from the inserted_at property, and using .toLocaleString() to format it nicely.

Conditional toasts

What if we want to conditionally display toasts based on certain criteria? We can do this by adding a show_toast property to our message payload in Knock, and setting it to false by default.

Then, in our code, we can add an if statement to only call the toast function if item.data.showToast is true:

if (item.data.showToast) {
  toast({
    title: `📨 New feed item at ${new Date(item.inserted_at).toLocaleString()}`,
    description: "Snap! This real-time feed is mind-blowing 🤯",
  });
}

Now, if we trigger a test event with showToast set to false, we'll see the feed update but no toast will be displayed. If we set it to true, we'll see both the feed update and the toast.

Conditionally displaying a toast

Using the data payload on a workflow trigger allows for a lot of customization. You could pass in all sorts of different properties and use them to determine how your UI should respond to different events.

Wrapping up

Real-time toasts are a pretty cool concept, and with Knock's feed API, you can power all sorts of in-app UI elements like banners, modals, or cards.

In this tutorial, we covered:

  • Installing and basic usage of the shadcn/ui toast component
  • Displaying a toast when the real-time feed receives a message
  • Conditionally displaying toasts based on the contents of the feed message

Using Knock's real-time capabilities and a bit of creative coding, the possibilities for engaging, responsive user interfaces are endless.

If you want to try out Knock to power your in-app notifications and toasts, you can sign up for free here. We have a generous free tier you can use to get started.

And as always, thanks for reading, and Knock on!