Tutorial February 28, 2026 · 8 min read

How to Show Unread Notification Count in React

Users expect a notification bell icon with a count badge that tells them something needs attention. This tutorial walks through building one from scratch in React — then shows how Notilayer gives you the same result in two minutes, with real-time updates and read/unread state management included.

Why Unread Notification Counts Matter

An unread notification count badge is one of the most effective engagement patterns on the web. Think of the red circle on Slack, GitHub, or LinkedIn — it creates a visual cue that pulls users back into the product without sending a single email.

For SaaS applications, unread counts serve a critical role: they tell your users that something happened while they were away — a teammate commented, an export finished, a payment failed. Without that signal, users have no reason to check your app proactively.

The challenge is that building a reliable unread count is harder than it looks. You need real-time delivery, persistent read states, cross-tab synchronization, and a UI component that updates without full page reloads. Let us walk through both approaches.

The Build vs Buy Decision

Before writing any code, consider the two paths available to you:

  • Build it yourself — full control, but you own the backend, the real-time layer, the state management, and every edge case that comes with it.
  • Use a tool like Notilayer — add two lines of code and get a bell icon, unread badge, inbox dropdown, and read/unread tracking out of the box.

We will cover both options below so you can make an informed choice. If you want the full comparison of notification tools, see our guide to adding a notification bell to any web app.

Option 1: Build It Yourself

Here is a minimal React component that renders a bell icon with an unread notification count badge. It polls your backend for the current count:

import { useState, useEffect } from 'react';

function NotificationBell() {
  const [unreadCount, setUnreadCount] = useState(0);

  useEffect(() => {
    const fetchCount = async () => {
      const res = await fetch('/api/notifications/unread-count');
      const data = await res.json();
      setUnreadCount(data.count);
    };

    fetchCount();
    const interval = setInterval(fetchCount, 10000); // poll every 10s
    return () => clearInterval(interval);
  }, []);

  return (
    <button style={{ position: 'relative' }}>
      🔔 {/* bell icon */}
      {unreadCount > 0 && (
        <span style={{
          position: 'absolute',
          top: -4, right: -4,
          background: 'red',
          color: 'white',
          borderRadius: '50%',
          width: 20, height: 20,
          fontSize: 12,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}>
          {unreadCount}
        </span>
      )}
    </button>
  );
}

This works for a basic prototype. You get a bell icon that shows a red badge with the number of unread notifications, updated every 10 seconds via polling. But as soon as you ship it to production, the problems start piling up.

The Problems With Building From Scratch

The component above looks simple, but it hides a long list of production requirements:

Real-time synchronization

Polling every 10 seconds means notifications can be up to 10 seconds stale. For a responsive experience, you need WebSockets or Server-Sent Events — plus reconnection logic, heartbeats, and offline handling.

Persistent read states

When a user reads a notification, that state needs to be stored server-side and synced across devices. If you only track read state in React state, a page refresh resets everything.

Cross-tab consistency

If a user has your app open in two tabs and marks a notification as read in one, the unread count in the other tab should update immediately. This requires shared state via BroadcastChannel, localStorage events, or a real-time server push.

Race conditions

New notifications arriving while the user is marking others as read can cause count mismatches. You need optimistic updates with server reconciliation to keep the badge accurate.

Backend infrastructure

You still need a notifications table, an API for creating and querying notifications, pagination, mark-as-read endpoints, and retention/cleanup policies. That is a full feature in itself.

For most teams, this adds up to 2–4 weeks of engineering work — and ongoing maintenance after that. If notifications are not your core product, there is a faster way.

Option 2: Use Notilayer (2 Minutes)

Notilayer is an embeddable notification widget that handles the bell icon, unread badge, inbox dropdown, and read/unread state management out of the box. Here is the full setup:

Step 1: Add the Widget Script

Add the Notilayer script to your HTML — index.html for CRA/Vite, or _document.tsx for Next.js:

<!-- Add before the closing </body> tag -->
<script src="https://api.notilayer.com/widget/widget.js" defer></script>

Step 2: Initialize the Widget

Call Notilayer.init() inside a useEffect hook after your user authenticates. The widget automatically renders a bell icon with an unread count badge — no extra component code needed:

import { useEffect } from 'react';

function App() {
  const user = useAuth(); // your auth hook

  useEffect(() => {
    if (user && window.Notilayer) {
      window.Notilayer.init({
        appId: 'YOUR_APP_ID',
        userId: user.id,
      });
    }
  }, [user]);

  return (
    {/* Your app content */}
  );
}

That is it for the frontend. The widget handles the unread count badge, the inbox dropdown, and read/unread state transitions automatically. For a detailed walkthrough of embedding in React, see our guide to adding a notification bell to a React app.

Step 3: Send Notifications From Your Backend

Use the Notilayer REST API to send notifications from your server. When a notification is created, the widget updates the unread count badge in real time — no polling required:

const response = await fetch('https://api.notilayer.com/v1/notify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_KEY',
  },
  body: JSON.stringify({
    userId: 'user_123',
    title: 'Your export is ready',
    body: 'Download your CSV file now.',
    url: '/exports/abc123',
  }),
});

The notification appears instantly in the user's inbox, and the unread badge count increments in real time. When the user opens the inbox or clicks the notification, it is automatically marked as read and the count decrements.

What You Get Out of the Box

With the three steps above, your React app now has a complete notification system. Here is everything the Notilayer widget provides without any additional code:

  • Bell icon — a clean, customizable notification bell that sits in your app's UI.
  • Unread count badge — a red badge that shows the exact number of unread notifications, updated in real time.
  • Inbox dropdown — a scrollable list of notifications with timestamps, titles, and body text.
  • Read/unread states — automatic state tracking that persists across sessions, devices, and tabs.
  • Real-time updates — new notifications appear instantly without polling, and counts update the moment a notification is read.
  • Mark all as read — a single action that clears the unread badge and marks every notification as read.

All of this would take weeks to build and maintain yourself. With Notilayer, it ships in two minutes. For more on how in-app notifications fit into your React SaaS product, check our complete React notification integration guide.

Key Takeaway

Showing an unread notification count badge in React looks simple on the surface, but production-quality implementation requires real-time delivery, persistent read states, cross-tab sync, and race condition handling. Notilayer gives you all of this — plus the bell icon, inbox dropdown, and mark-all-as-read — with a script tag and a single init() call. The free plan supports up to 1,000 monthly active users, so you can ship a working notification system today with zero cost.

Related Articles

Add an Unread Notification Badge to Your React App

Free plan included. No credit card required. Ship in under 2 minutes.