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.
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.
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.
activate, deactivate, isEnabled, and setEnabled live on
One.KeepAwake; the hook is its own export from one.
| Methods | Type | Description |
|---|---|---|
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. |
| Hook | Returns |
|---|---|
| useKeepAwake(tag?) | Nothing; holds a claim while mounted |
| iOS | Android | Web | |
|---|---|---|---|
| Mechanism | Idle timer flag | Window flag | Wake lock |
| Scope | Whole app | Foreground window | This page |
| Re-apply | Stays set | On every resume | Never |
| Refusal | Never refuses | Never refuses | Rejects |
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.
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.