skip to content

With expo-notifications, how do you schedule a daily local medication reminder in a React Native app, and which trigger types exist?

level: juniorimportance: must knowfreq 50%

answer

  1. local: scheduled on the device, no server
  2. scheduleNotificationAsync({ content, trigger })
  3. trigger type from SchedulableTriggerInputTypes
  4. returns an identifier to cancel
  5. weekday 1-7, Sunday is 1

basics

~10 s

Call Notifications.scheduleNotificationAsync with content and a trigger such as { type: SchedulableTriggerInputTypes.DAILY, hour, minute }; it resolves an identifier for cancelling. Other triggers: DATE, TIME_INTERVAL, WEEKLY, MONTHLY, YEARLY and iOS-only CALENDAR.

solid answer

~40 s

A local notification is scheduled on the device itself, so no server or push token is involved. `Notifications.scheduleNotificationAsync({ content, trigger })` takes the content (`title`, `body`, `data`) and a trigger object whose `type` comes from `Notifications.SchedulableTriggerInputTypes`: `DAILY` (`hour`, `minute`), `WEEKLY` (`weekday`, `hour`, `minute`), `MONTHLY`, `YEARLY`, `DATE` (one-off), `TIME_INTERVAL` (`seconds`, optional `repeats`) and `CALENDAR`, which is iOS-only. A `null` trigger delivers immediately. On Android the trigger also carries the `channelId`. The call resolves a string identifier: store it per medication so `cancelScheduledNotificationAsync(id)` can remove the reminder when the dose changes. Date components are validated: `weekday` runs 1-7 with 1 meaning Sunday, `month` 0-11, and out-of-range values throw.

code

typescript · 14 lines
typescript
import * as Notifications from 'expo-notifications';

export async function scheduleWeeklyDose(medId: string, jsDate: Date): Promise<string> {
  return Notifications.scheduleNotificationAsync({
    content: { title: 'Weekly injection', body: 'Time for your dose', data: { medId } },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
      weekday: jsDate.getDay() + 1, // getDay() is 0-6 from Sunday; weekday is 1-7 from Sunday
      hour: jsDate.getHours(),
      minute: jsDate.getMinutes(),
      channelId: 'meds',
    },
  });
}

go deeper

for a junior

Recall scheduleNotificationAsync with content and a trigger, the DAILY trigger's hour and minute, and that the call resolves an identifier you keep for cancelling.

for a middle

Explain the trigger types and their platform limits, the 1-7 weekday and 0-11 month ranges, and why a repeating interval is not a clock-time schedule.

for a senior

Treat the device schedule as state to reconcile: store identifiers per medication, cancel on change, avoid duplicates on launch and verify with getAllScheduledNotificationsAsync.

for a principal

Decide which reminders the device owns and which the server owns, and how the two stay consistent when a regimen changes on another device.

## Local versus push A **local notification** is created and scheduled by the app on the device. Nothing is sent from a server, no push token is needed, and the operating system delivers it at the scheduled moment even if the app is not running. That makes it the natural fit for **medication reminders**, whose times are known on the device. Two things still apply as for any notification: the app needs notification permission, and on Android the notification belongs to a channel. ## The call ```ts const id = await Notifications.scheduleNotificationAsync({ content: { title: 'Metformin 500 mg', body: 'Time for your morning dose', data: { medId: 'met' } }, trigger: { type: Notifications.SchedulableTriggerInputTypes.DAILY, hour: 8, minute: 30, channelId: 'meds' }, }); ``` - `content` holds what is shown (`title`, `subtitle`, `body`) and hidden `data` for the app. - `trigger` says when; every trigger object carries a `type`. - The promise resolves an **identifier**, used later to cancel or to recognise the notification. ## The trigger types | Type | Fields | Repeats | Platforms | |---|---|---|---| | `DATE` | `date` (a `Date` or timestamp) | Never | Both | | `TIME_INTERVAL` | `seconds`, `repeats?` | If `repeats: true` | Both; on iOS a repeating interval must be at least 60 s | | `DAILY` | `hour`, `minute` | Every day | Both | | `WEEKLY` | `weekday`, `hour`, `minute` | Every week | Both | | `MONTHLY` | `day`, `hour`, `minute` | Every month | Both | | `YEARLY` | `month`, `day`, `hour`, `minute` | Every year | Both | | `CALENDAR` | date components, `repeats?` | If `repeats: true` | iOS only | A `null` trigger means "deliver now", and passing a bare `Date` or number is treated as a `DATE` trigger. Every trigger accepts an optional `channelId` for Android. ## Date components are validated, and not all are zero-based expo-notifications checks the components before scheduling and throws a `RangeError` when one is out of range: - `hour` 0-23 and `minute` 0-59; - `weekday` **1-7, where 1 is Sunday**, which differs from JavaScript's `Date.getDay()` (0 is Sunday); - `month` **0-11**, matching JavaScript's `Date`; - `day` from 1 to the number of days in that month. Passing `new Date().getDay()` straight into a weekly trigger therefore schedules every reminder one day early, and throws on Sundays. ## Managing the schedule 1. **Store the identifier** with the medication, so a changed dose time can cancel exactly its own reminders. 2. **Cancel** with `Notifications.cancelScheduledNotificationAsync(id)`, or everything with `cancelAllScheduledNotificationsAsync()`. 3. **Inspect** with `getAllScheduledNotificationsAsync()`, and preview a trigger with `getNextTriggerDateAsync(trigger)`. 4. **Reconcile on launch**: compare the stored plan with what is scheduled and fix drift, rather than scheduling duplicates every time the app opens. ## Designing a medication schedule A regimen rarely maps to a single trigger. Typical translations: - **Twice daily** (08:00 and 20:00): two `DAILY` triggers, one identifier each. - **Once a week** on Mondays: one `WEEKLY` trigger with `weekday: 2`. - **A 10-day course**: `DAILY` triggers have no end date, so either schedule ten `DATE` triggers or keep a `DAILY` trigger and cancel it when the course ends. - **Every 8 hours from the first dose**: a repeating `TIME_INTERVAL` of 28,800 seconds, which is anchored to when it was scheduled, matching the regimen. Store the regimen as data and derive the scheduled notifications from it, so a change to the regimen always produces the same set of triggers. ## Presentation when the app is open Scheduling does not guarantee a banner. If the reminder fires while the app is in the foreground, expo-notifications shows it only if a notification handler says so; that is the same foreground rule as for push. ## Local or push for reminders Interviewers often ask why not send each reminder from a server. For fixed-time reminders, local scheduling wins: it works offline, needs no token or backend job, and fires even if the network is down at dose time. A server is still useful to back up the regimen and to change it from another device, after which the app reschedules locally. ## Common mistakes - Using a repeating 24-hour `TIME_INTERVAL` for "every day at 08:30": it repeats from the moment of scheduling, not at 08:30. - Passing `Date.getDay()` as `weekday`. - Using `CALENDAR` and expecting it to work on Android. - Scheduling on every launch without cancelling, which piles up duplicate reminders.

  • Why is a repeating TIME_INTERVAL of 86400 seconds a poor way to remind someone every day at 08:30?
    A repeating interval counts from the moment it was scheduled, so a reminder set at 14:10 fires at 14:10 every day, not at 08:30. The `DAILY` trigger matches the `hour` and `minute` components instead, which is what a medication time means.
  • How should the app update a reminder when the user moves a dose from 08:30 to 09:00?
    Cancel the old reminder with `cancelScheduledNotificationAsync` using the identifier stored for that medication, schedule the new `DAILY` trigger, and store the new identifier. Scheduling without cancelling leaves both reminders active.

saying these in an interview costs you the question

  • Local notifications need a push token and a server to fire
  • The CALENDAR trigger works the same on Android and iOS
  • WEEKLY weekday values use JavaScript's getDay() numbering
  • A repeating 24-hour TIME_INTERVAL fires at the same clock time each day
  • Scheduling the same reminder on every launch is harmless