One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Keep Awake

Keep the display from dimming while a screen needs it

One.KeepAwake holds the display on while a screen needs to stay visible: a workout timer, a recipe step, a boarding pass. Activate a tag when the work starts, deactivate the same tag when the work ends. The screen stays awake while any tag is active.

On iOS it flips the app-wide isIdleTimerDisabled flag on the main thread. On Android it sets the foreground window’s keep-screen-on flag and re-applies it on resume. On web it holds a screen wake lock.

Usage

import { One, useKeepAwake } from 'one'
function WorkoutScreen() {
useKeepAwake('workout')
return null
}
async function holdForStep() {
await One.KeepAwake.activate('recipe-step')
}
async function releaseStep() {
await One.KeepAwake.deactivate('recipe-step')
}

The hook scopes the wake lock to the mounted screen: it activates on mount and deactivates in the cleanup, so nothing else can release it early. The imperative pair suits work that starts and ends outside render, such as a timer callback. Each activation needs its own deactivation with the same tag.

How it works

Active tags form one app-wide set, and the screen stays awake while the set is non-empty. Two screens holding different tags never release each other; each tag needs its own deactivation. Calling activate without a tag uses the shared default tag, as does deactivate without one, so the two pair up. setEnabled(true) activates the default tag and setEnabled(false) deactivates it, which keeps single-flag callers working. Tags must be strings when given; anything else throws synchronously with the same message on web and native. isEnabled reads whether any tag is active on every call.

useKeepAwake without a tag activates an id unique to its component instance rather than the default tag, matching Expo, so mounting the same screen twice holds two independent claims. With a tag it shares that tag with any matching imperative call.

On Android the tag set is held in memory and applied to the foreground window, then re-applied when the activity resumes, so recreation never drops it. On web activate waits for the actual screen lock and rejects when the browser lacks wake locks, the user refuses, or the document is hidden; isEnabled reads the current lock sentinel, so a browser-side release turns it false. A released lock is not reacquired when the page becomes visible again. Server rendering accepts the calls, changes nothing, and reads false. The tags clear to the system default when the app process ends.

API reference

activate, deactivate, isEnabled, and setEnabled live on One.KeepAwake; the hook is its own export from one.

MethodsTypeDescription
isEnabled() => Promise<boolean>Whether any tag is active.
() => Promise<boolean>Whether any tag is active.
setEnabled(enabled: boolean) => Promise<void>Activate or deactivate the default tag.
(enabled: boolean) => Promise<void>Activate or deactivate the default tag.
activate(tag: string) => Promise<void>Hold the display on under a tag. Omit the tag for the shared default.
(tag: string) => Promise<void>Hold the display on under a tag. Omit the tag for the shared default.
deactivate(tag: string) => Promise<void>Release a tag. The screen sleeps when no tags remain.
(tag: string) => Promise<void>Release a tag. The screen sleeps when no tags remain.
HookReturns
useKeepAwake(tag?)Nothing; holds a claim while mounted

Platform differences

iOSAndroidWeb
MechanismIdle timer flagWindow flagWake lock
ScopeWhole appForeground windowThis page
Re-applyStays setOn every resumeNever
RefusalNever refusesNever refusesRejects

Limits and gotchas

Examples

Hold the screen for a timer

import { Text } from 'react-native'
import { useKeepAwake } from 'one'
function WorkoutTimer({ secondsLeft }: { secondsLeft: number }) {
useKeepAwake('workout')
return <Text>{secondsLeft > 0 ? `${secondsLeft}s left, staying on` : 'Done'}</Text>
}

The screen holds the workout tag for its whole mount, so the lock releases when the workout screen unmounts. For work that ends while its screen stays mounted, use the imperative pair instead.

Check before a long step

import { One } from 'one'
async function ensureAwake(needed: boolean) {
if (!needed) return
if (await One.KeepAwake.isEnabled()) return
await One.KeepAwake.activate('long-step')
}

Reading first avoids redundant native calls when another tag already holds the screen. The caller that activates long-step must deactivate it; nothing else releases that tag.

Edit this page on GitHub.