Back to Blog

Prevent React Native Timers from Stopping in Background: A Complete Guide

Royan Gagas
December 26, 2025
Share
product
react native
mobile application
Prevent React Native Timers from Stopping in Background: A Complete Guide

Building a habit tracking app is a fun challenge. For my latest project, I decided to gamify the experience with a UI inspired by dating apps, swipe left to skip a habit, swipe right to complete it.

It was all smooth sailing until I implemented "Duration Habits" (e.g., "Medidate for 20 minutes"). The UI looked great, but I immediately hit a classic React Native roadblock:

The moment the user locked their phone or switched to another app, the Javascript thread paused, and my timer stopped dead.

For a meditation or workout timer, this is broken functionality. The user expects the timer to alert them when they are done, regardless of whether the app is open.

In this post, I'll walk through how I solved this using Android Foreground Services via react-native-background-actions, and crucially, how I synchronized that background process with my modern state management solution, Zustand.

The Challenge: React Native vs. The OS

Mobile operating systems are aggresive about saving battery. When an app moves to the background, iOS and Android will suspend its Javascript execution thread almost immediately.

To keep code running, we need a special permit from the OS. On Android, this called a Foreground Service. It tells the OS, "Hey, I'm doing something important that the user is aware of, please don't kill me." This requires showing a persistent notification in the status bar so the user knows the app is still active.

The Stack

React Native Expo (Dev Client) or bare workflow.
State Management: Zustand (This is important later).
The Hero Library: react-native-background-actions

Step 1: Setup and Configuration

first, get the library installed:

Android Configuration is Mandatory

You cannot skip this step. You need to declare the necessary permission and the service itself in your android/app/src/main/AndroidManifest.xml file. Without this, the app will crash when you try to start the background task.

Open AndroidManifest.xml and add these lines inside the <manifest> tag and <application> tag respectively:

Step 2: The State Synchronization Problem

This is where most developers get stuck.

A background task created by this library runs in a Javascript context that is separate from your React Component lifecyle.

Crucial realization: You cannot use standard React Hooks like useState, useEffect, or custom Zustand hooks like useHabitStore() inside the background task loop. They won't update or react as you expect.

The Solution: Imperative State Access

Since we are using Zustand, we have an escape hatch. Instead of using reactive hook inside the background loop, we will access the store directly and imperatively using useHabitStore.getState().

Step 3: The Background Task Function

This is the heart of the operation. This function defines what happens while the app is in the background. It's essentially an infinite loop that only breaks when the timer finishes or the user pauses it in the UI.

Here is the real-world code adapted for a timer:

Step 4: Triggering the Service from the UI

Now we need to connect our React Native UI to this background task. When the user taps "Start Timer" on a habit card, the service should spin up. When they tap "Pause", it should tear down.

We use useEffect within our timer component to monitor changes in our Zustand state and react accordingly.

Key Takeaways for Implementation

If you are implementing this in your own app, keep these lessons in mind:

1.State Management Paradox: Remember, the background task is just a standard JS function, not a React component. You cannot use hooks. If you use Redux or Zustand, you must use their imperative APIs (like store.getState() and store.dispatch()) to read and write data inside the background loop.
2.The Notification Loop: The BackgroundService.updateNotification allows you to make the notification feel "alive." Updating the progress bar and timer text every second assures the user that the app hasn't crashed while in their pocket.
3.Clean Up is Vital: The useEffect dependency array is crucial. When timerIsRunning flips to false in your UI, you must call BackgroundService.stop(). Otherwise, you'll leave "ghost processes" running that drain battery and annoy users.
4.Robuts Timing: Notive in the background task I used Date.now() subtraction rather than just incrementing a counter like elapsed++. Using system time differences ensures that even if the OS briefly pauses execution, the timer calculation remains accurate when it wakes back up.

This approach provided the robust "set it and forget it" experience my habit tracker needed, matching the sleekness of the swipe-based UI.