react-native-otp-timer-hook
v0.0.2
Published
A customizable OTP timer hook and UI component with background support for React Native.
Downloads
6
Readme
react-native-otp-timer
A powerful, customizable, and performance-optimized OTP (One-Time Password) timer component for React Native applications. Perfect for implementing secure authentication flows with automatic resend functionality and background time tracking.
Features
- ⏱️ Accurate Timing: Precise countdown with background time calculation
- 🔄 Auto Resend: Configurable resend attempts with loading states
- 📱 Background Aware: Continues timing when app is backgrounded
- 🎨 Fully Customizable: Extensive styling and text formatting options
- ⚡ Performance Optimized: Built with React.memo and stable references
- 🔧 TypeScript Support: Fully typed with comprehensive interfaces
- 🧪 Testing Friendly: Debug mode and testing utilities included
- 🎯 Flexible API: Use as component or hook only
- 🚀 Zero Dependencies: No external dependencies except React Native
Installation
npm install react-native-otp-timer
# or
yarn add react-native-otp-timer.
Quick Start
import React from 'react';
import { View, Alert } from 'react-native';
import OtpTimer from 'react-native-otp-timer';
const App = () => {
const handleResend = async (attemptNumber: number) => {
// Your OTP resend logic here
console.log(`Resending OTP - attempt ${attemptNumber}`);
await fetch('/api/resend-otp', { method: 'POST' });
};
const handleTimeout = () => {
Alert.alert('Timeout', 'OTP has expired');
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<OtpTimer
initialTimer={300} // 5 minutes
resendLimit={3}
onResend={handleResend}
onTimeout={handleTimeout}
/>
</View>
);
};
export default App;Advanced Usage
Fully Customized Implementation
import React, { useCallback } from 'react';
import { StyleSheet } from 'react-native';
import OtpTimer from 'react-native-otp-timer';
const CustomOtpTimer = () => {
const handleResend = useCallback(async (count: number) => {
try {
const response = await api.resendOtp({ attempt: count });
showSuccess('OTP sent successfully');
} catch (error) {
showError('Failed to send OTP');
throw error; // Prevent timer reset on failure
}
}, []);
const formatTime = useCallback((seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}m ${secs}s remaining`;
}, []);
return (
<OtpTimer
initialTimer={600} // 10 minutes
resendLimit={5}
onResend={handleResend}
onTimeout={() => navigation.goBack()}
onLimitExceeded={() => showContactSupport()}
containerStyle={styles.container}
textStyle={styles.text}
linkStyle={styles.link}
formatText={formatTime}
formatResendText={(attempt, max) => `Resend (${attempt}/${max})`}
showAttemptCounter={true}
debug={__DEV__}
/>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#f8f9fa',
padding: 16,
borderRadius: 8,
},
text: {
fontSize: 16,
color: '#495057',
},
link: {
fontSize: 16,
color: '#007bff',
fontWeight: '600',
},
});Hook-Only Usage
import { useOtpTimer } from 'react-native-otp-timer';
const CustomTimerComponent = () => {
const { timer, resetTimer, isExpired, pauseTimer, resumeTimer } = useOtpTimer({
initialTimer: 300,
onTimeout: () => console.log('Timer expired'),
onTick: (currentTime) => {
if (currentTime === 60) {
showWarning('1 minute remaining!');
}
},
debug: true,
});
// Your custom UI implementation
return (
<View>
<Text>Time: {timer}s</Text>
<Button title="Reset" onPress={resetTimer} />
</View>
);
};API Reference
OtpTimer Component Props
| Prop | Type | Default | Description |
| ---------------------- | ------------------------------------------ | --------------------- | --------------------------------------- |
| initialTimer | number | Required | Initial countdown time in seconds |
| resendLimit | number | Required | Maximum number of resend attempts |
| onResend | (count: number) => Promise<void> | Required | Callback for handling OTP resend |
| onTimeout | () => void | undefined | Called when timer reaches zero |
| onLimitExceeded | () => void | undefined | Called when resend limit is exceeded |
| onTimerStart | (seconds: number) => void | undefined | Called when timer starts/resets |
| containerStyle | ViewStyle | undefined | Custom container styling |
| textStyle | TextStyle | undefined | Custom text styling |
| linkStyle | TextStyle | undefined | Custom link/button styling |
| limitExceededStyle | TextStyle | undefined | Custom style for limit exceeded message |
| formatText | (seconds: number) => string | "MM:SS" | Custom timer display formatter |
| formatResendText | (attempt: number, max: number) => string | "Resend" | Custom resend button text |
| limitExceededMessage | string | "Limit exceeded..." | Custom limit exceeded message |
| enabled | boolean | true | Enable/disable timer functionality |
| showAttemptCounter | boolean | false | Show attempt counter in UI |
| debug | boolean | false | Enable debug logging |
useOtpTimer Hook
Parameters
interface UseOtpTimerProps {
initialTimer: number;
onTimeout: () => void;
onTick?: (currentTime: number) => void;
debug?: boolean;
}Returns
interface UseOtpTimerReturn {
timer: number; // Current timer value in seconds
resetTimer: () => void; // Reset timer to initial value
isActive: boolean; // Whether timer is currently running
isExpired: boolean; // Whether timer has reached zero
pauseTimer: () => void; // Pause the timer
resumeTimer: () => void; // Resume paused timer
}Performance Best Practices
1. Memoize Callbacks
Always use useCallback for your event handlers:
const handleResend = useCallback(async (count: number) => {
// Your resend logic
}, []);
const handleTimeout = useCallback(() => {
// Your timeout logic
}, []);2. Stable Props
Avoid creating objects in render:
// ❌ Bad - creates new object on every render
<OtpTimer
containerStyle={{ padding: 16 }}
onResend={(count) => api.resend(count)}
/>
// ✅ Good - stable references
const containerStyle = { padding: 16 };
const handleResend = useCallback((count) => api.resend(count), []);
<OtpTimer
containerStyle={containerStyle}
onResend={handleResend}
/>3. Component Memoization
The OtpTimer component is already memoized, but ensure parent components don't cause unnecessary re-renders:
const ParentComponent = React.memo(() => {
// Component implementation
});Background Behavior
The timer automatically handles app state changes:
- Background: Records timestamp when app goes to background
- Foreground: Calculates time spent in background and updates timer accordingly
- Accuracy: Maintains precise timing regardless of background duration
Error Handling
The component provides several ways to handle errors:
const handleResend = async (count: number) => {
try {
await api.resendOtp();
// Success - timer will reset automatically
} catch (error) {
showError('Failed to send OTP');
throw error; // Re-throw to prevent timer reset
}
};Testing
Debug Mode
Enable debug mode for development:
<OtpTimer
debug={__DEV__}
// ... other props
/>Testing Configuration
Use shorter timers and higher limits for testing:
const TIMER_CONFIG = __DEV__
? { initialTimer: 10, resendLimit: 10 }
: { initialTimer: 300, resendLimit: 3 };
<OtpTimer
{...TIMER_CONFIG}
// ... other props
/>Migration Guide
From Basic Timer Libraries
If you're migrating from a basic countdown timer:
- Replace timer prop with
initialTimer - Add required
onResendcallback - Update styling props (most are compatible)
- Add error handling to resend function
Performance Considerations
- Callbacks are automatically memoized internally
- Component uses React.memo for render optimization
- Background timing prevents unnecessary re-renders
- All intervals and listeners are properly cleaned up
Examples
Check out the examples directory for complete implementation examples including:
- Basic usage
- Custom styling
- Redux integration
- Error handling
- Testing setups
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see the LICENSE file for details.
Made with ❤️ for the React Native community
Author
👤 Hamza Gulraiz
📬 Support me
