react-native-otp-timer-lite
v0.1.0
Published
A lightweight OTP resend countdown timer for React Native — background-accurate hook plus an optional drop-in component.
Maintainers
Readme
react-native-otp-timer-lite
A lightweight OTP resend countdown timer for React Native.
Ships a headless useOtpTimer hook for building your own UI, plus an optional
<OtpTimer /> component when you just want something that works. No native
modules — pure TypeScript, so it drops into Expo and bare React Native alike.
Background-accurate. The countdown is derived from a wall-clock deadline
rather than from a count of setInterval ticks. When the OS throttles or
suspends JS timers — which it does as soon as your app is backgrounded — the
remaining time is still correct the moment the user returns. The hook also
re-syncs on AppState change, so the UI snaps to the right value immediately
on resume instead of drifting for a second first.
Installation
npm install react-native-otp-timer-liteyarn add react-native-otp-timer-litereact and react-native are peer dependencies — no extra linking, pods, or
config required.
Usage
The hook (build your own UI)
TypeScript
import React from 'react';
import { Button, Text, View } from 'react-native';
import { useOtpTimer } from 'react-native-otp-timer-lite';
export function VerifyScreen() {
const { formatted, isRunning, start } = useOtpTimer(30);
const handleResend = () => {
sendOtp();
start(); // restart the countdown
};
return (
<View>
{isRunning ? (
<Text>Resend OTP in {formatted}</Text>
) : (
<Button title="Resend OTP" onPress={handleResend} />
)}
</View>
);
}JavaScript
import React from 'react';
import { Button, Text, View } from 'react-native';
import { useOtpTimer } from 'react-native-otp-timer-lite';
export function VerifyScreen() {
const { formatted, isRunning, start } = useOtpTimer(30);
const handleResend = () => {
sendOtp();
start();
};
return (
<View>
{isRunning ? (
<Text>Resend OTP in {formatted}</Text>
) : (
<Button title="Resend OTP" onPress={handleResend} />
)}
</View>
);
}The component (drop-in)
TypeScript
import React from 'react';
import { OtpTimer } from 'react-native-otp-timer-lite';
export function VerifyScreen() {
return (
<OtpTimer
seconds={30}
onResend={() => sendOtp()}
textStyle={{ color: '#888' }}
/>
);
}JavaScript
import React from 'react';
import { OtpTimer } from 'react-native-otp-timer-lite';
export function VerifyScreen() {
return <OtpTimer seconds={30} onResend={() => sendOtp()} />;
}By default the component renders Resend OTP in 00:30 while counting down,
then swaps to a tappable Resend OTP label at zero — which fires onResend
and restarts the countdown.
Custom rendering
renderContent hands you the timer state and takes over rendering entirely, so
you keep the timing logic without the default markup:
<OtpTimer
seconds={60}
onResend={sendOtp}
renderContent={({ formatted, isRunning, resend }) => (
<Pressable onPress={resend} disabled={isRunning}>
<Text>{isRunning ? `Wait ${formatted}` : 'Send again'}</Text>
</Pressable>
)}
/>Not starting immediately
Pass autoStart: false when the countdown should begin only after the OTP is
actually sent:
const { formatted, isRunning, start } = useOtpTimer(30, { autoStart: false });
const sendCode = async () => {
await api.sendOtp();
start();
};API reference
useOtpTimer(seconds, options?)
Parameters
| Parameter | Type | Default | Description |
| --------- | -------------------- | ---------- | ------------------------------------ |
| seconds | number | required | Countdown duration in seconds. |
| options | UseOtpTimerOptions | {} | Optional configuration (see below). |
options
| Option | Type | Default | Description |
| ----------- | ------------ | ------- | ------------------------------------------------------------ |
| autoStart | boolean | true | Start counting down as soon as the hook mounts. |
| onExpire | () => void | — | Called once each time the countdown reaches zero. |
| interval | number | 1000 | How often the displayed value refreshes, in milliseconds. |
Returns
| Property | Type | Description |
| ----------- | ----------------------------- | ---------------------------------------------------------------------------------- |
| timeLeft | number | Whole seconds remaining. Never negative. |
| isRunning | boolean | true while the countdown is active; false once it hits zero or is reset. |
| formatted | string | timeLeft rendered as MM:SS — e.g. "00:30", "02:05". |
| start | (seconds?: number) => void | Begin or restart the countdown. Pass a value to override the duration for this run. |
| reset | (seconds?: number) => void | Stop the countdown and restore the full duration without starting it. |
<OtpTimer />
| Prop | Type | Default | Description |
| ----------------- | ------------------------------------------ | ------------------------ | --------------------------------------------------------------------------- |
| seconds | number | 30 | Countdown duration in seconds. |
| onResend | () => void | — | Fired when the user taps resend after the countdown finishes. |
| autoStart | boolean | true | Begin counting down on mount. |
| restartOnResend | boolean | true | Restart the countdown automatically after a resend. |
| textTemplate | string | 'Resend OTP in {time}' | Text shown while counting down. {time} is replaced with the MM:SS value. |
| resendLabel | string | 'Resend OTP' | Label for the resend control. |
| onExpire | () => void | — | Called once each time the countdown reaches zero. |
| containerStyle | StyleProp<ViewStyle> | — | Style applied to the wrapping View. |
| textStyle | StyleProp<TextStyle> | — | Style applied to the countdown text. |
| resendTextStyle | StyleProp<TextStyle> | — | Style applied to the resend label. |
| renderContent | (state) => React.ReactElement \| null | — | Overrides the default rendering entirely. Receives { timeLeft, formatted, isRunning, resend }. |
| testID | string | — | Applied to the container; the resend control gets `${testID}-resend`. |
Notes
onResendis only reachable once the countdown has finished — the resend control is not rendered while the timer is running, so users cannot spam it.reset()stops the timer and restores the full duration;start()is what actually begins counting. Callstart()if you want reset-and-run.onExpirefires exactly once per run, even if the app was backgrounded across the moment the timer hit zero.
License
MIT © Melby Thomas
