Push Notifications API Reference

Use useNotification to schedule future one-time or recurring push notifications for reminders and alerts. Push notifications are for important scheduled messages, not immediate in-app feedback.

useNotification

Import the hook from the notification module:

import { useNotification } from "@/hooks/use-notification";

React hook rules apply: call useNotification only at the top level of React function components.

const {
  scheduleNotification,
  cancelNotification,
  scheduleNotifications,
  cancelNotifications,
  checkNotificationStatus,
  isLoading,
  error,
  clearError,
} = useNotification();

Return values:

  • scheduleNotification ((title, body, options) => Promise<string | undefined>): Schedule one notification. Returns the notification ID when scheduling succeeds.
  • cancelNotification ((id: string) => Promise<boolean>): Cancel one scheduled notification.
  • scheduleNotifications ((notifications) => Promise<Array<{ success; notificationId?; error?; index }>>): Schedule multiple notifications in bulk.
  • cancelNotifications ((ids: string[]) => Promise<Array<{ success; notificationId; error? }>>): Cancel multiple notifications in bulk.
  • checkNotificationStatus (() => Promise<{ enabled: boolean; subscribed: boolean }>): Check whether notifications are available for this app.
  • isLoading (boolean): true while an operation is in progress.
  • error (Error | null): Notification error state.
  • clearError (() => void): Clear the current error.

Use push notifications for time-sensitive reminders, recurring reminders, deadlines, appointments, and scheduled alerts. Use toast from "sonner" for immediate user feedback.

Scheduling Options

Every notification must be scheduled for future delivery. Specify either scheduledFor for a one-time notification or schedule for a recurring notification.

One-time notification:

{
  scheduledFor: Date | string;
}

scheduledFor can be a Date or an ISO 8601 UTC string.

Recurring notification:

{
  schedule: {
    minute: number;
    hour: number;
    daysOfWeek?: number[];
    daysOfMonth?: number[];
    timeZone?: string;
  }
}

Recurring fields:

  • minute: 0 through 59.
  • hour: 0 through 23.
  • daysOfWeek: Optional days where 0 is Sunday and 6 is Saturday.
  • daysOfMonth: Optional month days from 1 through 31. Do not use with daysOfWeek.
  • timeZone: Optional IANA timezone such as "America/Los_Angeles".

UX Patterns

Push notifications require two user-level prerequisites: browser notification permission and app-specific notification enablement.

For normal reminder creation, call scheduleNotification() directly. The backend enforces prerequisites, and the hook shows the API error message in a toast when no custom onError handler is provided.

const notificationId = await scheduleNotification(
  "Reminder",
  "Don't forget!",
  { scheduledFor: new Date(someDate).toISOString() }
);

if (!notificationId) {
  setNotificationError(
    "Notifications aren't enabled yet. Select Notifications at the top of the screen to turn them on."
  );
  return;
}

setReminders((prev) => [
  ...prev,
  {
    id: crypto.randomUUID(),
    text: "Don't forget!",
    time: new Date(someDate).toISOString(),
    notificationId,
  },
]);

Use checkNotificationStatus() for proactive settings screens, not as a required preflight before every schedule call.

const status = await checkNotificationStatus();

if (!status.subscribed || !status.enabled) {
  setNotificationError(
    "Notifications aren't enabled yet. Select Notifications at the top of the screen to turn them on."
  );
  return;
}

Do not update app state that depends on notification delivery when scheduling fails. For example, do not add a reminder, enable a reminder toggle, or store a notification ID unless scheduling returns a real ID.

Examples

One-time reminder with cancellation:

import * as React from "react";
import { useNotification } from "@/hooks/use-notification";
import { usePersistentItem } from "@/hooks/use-persistent-item";

interface Reminder {
  id: string;
  text: string;
  time: string;
  notificationId: string;
}

const NOTIFICATIONS_NOT_ENABLED_MESSAGE =
  "Notifications aren't enabled yet. Select Notifications at the top of the screen to turn them on.";

export default function App() {
  const { scheduleNotification, cancelNotification, isLoading } =
    useNotification();
  const [reminders, setReminders] = usePersistentItem<Reminder[]>(
    "reminders",
    []
  );
  const [text, setText] = React.useState("");
  const [dateTime, setDateTime] = React.useState("");
  const [notificationError, setNotificationError] = React.useState<string | null>(
    null
  );

  const addReminder = async () => {
    if (!text || !dateTime) return;

    const isoString = new Date(dateTime).toISOString();
    const notificationId = await scheduleNotification("Reminder", text, {
      scheduledFor: isoString,
    });

    if (!notificationId) {
      setNotificationError(NOTIFICATIONS_NOT_ENABLED_MESSAGE);
      return;
    }

    setNotificationError(null);
    setReminders((prev) => [
      ...prev,
      { id: crypto.randomUUID(), text, time: isoString, notificationId },
    ]);
    setText("");
    setDateTime("");
  };

  const deleteReminder = async (reminder: Reminder) => {
    const cancelled = await cancelNotification(reminder.notificationId);
    if (cancelled) {
      setReminders((prev) => prev.filter((item) => item.id !== reminder.id));
    }
  };

  return (/* render reminder form, error, isLoading, and reminder list */);
}

Daily recurring notification:

const notificationId = await scheduleNotification(
  "Daily Standup",
  "Time for your daily standup meeting!",
  {
    schedule: {
      hour: 9,
      minute: 0,
    },
  }
);

Weekday recurring notification:

const notificationId = await scheduleNotification(
  "Time to Log Hours",
  "Don't forget to log your work hours for today",
  {
    schedule: {
      hour: 17,
      minute: 0,
      daysOfWeek: [1, 2, 3, 4, 5],
    },
  }
);

Monthly recurring notification:

const notificationId = await scheduleNotification(
  "Monthly Report Due",
  "Time to submit your monthly progress report",
  {
    schedule: {
      hour: 10,
      minute: 0,
      daysOfMonth: [1],
    },
  }
);

Date and Time Handling

HTML date and time inputs return local strings. Convert them to UTC ISO strings before scheduling one-time notifications.

// <input type="date"> returns "YYYY-MM-DD"
// <input type="time"> returns "HH:mm"
// <input type="datetime-local"> returns "YYYY-MM-DDTHH:mm"

const dateValue = "2025-08-21";
const timeValue = "14:30";
const localDateTime = new Date(`${dateValue}T${timeValue}`);
const utcString = localDateTime.toISOString();

const datetimeLocalValue = "2025-08-21T14:30";
const utcFromDateTimeLocal = new Date(datetimeLocalValue).toISOString();

Best Practices

  • Schedule directly by default; do not preflight every call with checkNotificationStatus().
  • If scheduleNotification() returns undefined, stop and do not persist dependent state.
  • Reserve checkNotificationStatus() for settings screens or proactive capability UI.
  • Store successful notification IDs with usePersistentItem so reminders can be cancelled later.
  • Always provide a way to cancel scheduled notifications.
  • Convert local one-time reminder values to UTC with toISOString().
  • Use either scheduledFor or schedule, never both.
  • Store an inline error near important controls in addition to relying on toast feedback.