A React Native library for detecting user idle time with automatic pause/resume support for keyboard and app state changes.
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-12-29.at.13.32.33.mp4
To see the demo implementation, check out the Demo/ folder which contains a complete Expo app showcasing all features of the library including:
- Real-time countdown display
- Timer state visualization (running/paused/idle)
- Manual pause/resume controls
- Interactive test area for activity detection
- Idle modal demonstration
- Complete API method examples
To run the demo app:
pnpm demo:start
# or
pnpm demo:ios
pnpm demo:android
pnpm demo:webThe demo implementation serves as a reference for how to use the library. You can view the source code in Demo/src/DemoScreen.tsx to see practical examples of integrating useIdleTimer into your React Native components.
- ⏱️ Configurable timeout - Set custom idle timeout duration
- 🎯 Automatic detection - Detects touch events, keyboard interactions, and app state changes
- ⏸️ Pause/Resume - Manually pause or resume the timer with composable pause reasons
- 🔄 State management - Track idle state, remaining time, and timer status
- 📱 React Native optimized - Uses PanResponder for efficient touch detection
- 🎣 Hook & Context - Use as a hook or context provider for app-wide idle detection
- 📝 TypeScript - Full TypeScript support with type definitions
- 🎨 Event callbacks -
onIdle,onActive, andonActioncallbacks for custom logic
npm install react-native-idle-timer-detection
# or
yarn add react-native-idle-timer-detection
# or
pnpm add react-native-idle-timer-detectionimport { useIdleTimer } from "react-native-idle-timer-detection";
function MyComponent() {
const idleTimer = useIdleTimer({
timeout: 30, // 30 seconds
onIdle: () => {
console.log("User is idle");
},
onActive: () => {
console.log("User is active");
},
});
return (
<View {...idleTimer.panResponder.panHandlers}>
<Text>Remaining: {idleTimer.getRemainingTime()}s</Text>
<Text>State: {idleTimer.getCurrentState()}</Text>
<Text>Is Idle: {idleTimer.getIsIdle() ? "Yes" : "No"}</Text>
</View>
);
}import {
IdleTimerProvider,
useIdleTimerContext,
} from "react-native-idle-timer-detection";
function App() {
return (
<IdleTimerProvider
timeout={30}
onIdle={() => console.log("User is idle")}
onActive={() => console.log("User is active")}
>
<MyApp />
</IdleTimerProvider>
);
}
function MyComponent() {
const idleTimer = useIdleTimerContext();
return (
<View>
<Text>Remaining: {idleTimer.getRemainingTime()}s</Text>
</View>
);
}import { useIdleTimer } from "react-native-idle-timer-detection";
function MyScreen() {
const idleTimer = useIdleTimer({
timeout: 60, // 1 minute
onIdle: () => {
// Show idle modal or logout user
console.log("User has been idle for 60 seconds");
},
onActive: () => {
// Hide idle modal
console.log("User is active again");
},
onAction: () => {
// Track user activity
console.log("User performed an action");
},
});
return (
<View {...idleTimer.panResponder.panHandlers}>
<Text>Time remaining: {idleTimer.getRemainingTime()}s</Text>
</View>
);
}function MyComponent() {
const idleTimer = useIdleTimer({ timeout: 30 });
const handlePause = () => {
idleTimer.pause("manual");
};
const handleResume = () => {
idleTimer.resume("manual");
};
return (
<View>
<Button title="Pause" onPress={handlePause} />
<Button title="Resume" onPress={handleResume} />
<Text>State: {idleTimer.getCurrentState()}</Text>
</View>
);
}The timer supports multiple pause reasons. The timer will only resume when all pause reasons are cleared:
function MyComponent() {
const idleTimer = useIdleTimer({ timeout: 30 });
// Pause for app state
idleTimer.pause("appstate");
// Pause for keyboard
idleTimer.pause("keyboard");
// Resume keyboard (timer still paused due to appstate)
idleTimer.resume("keyboard");
// Resume appstate (timer now running)
idleTimer.resume("appstate");
}import { useEffect, useState } from "react";
import { useIdleTimer } from "react-native-idle-timer-detection";
function CountdownDisplay() {
const [remainingTime, setRemainingTime] = useState(0);
const idleTimer = useIdleTimer({ timeout: 30 });
useEffect(() => {
const interval = setInterval(() => {
setRemainingTime(idleTimer.getRemainingTime());
}, 1000);
setRemainingTime(idleTimer.getRemainingTime());
return () => clearInterval(interval);
}, [idleTimer]);
return (
<View>
<Text style={{ fontSize: 48 }}>{remainingTime}</Text>
<Text>seconds remaining</Text>
<Text>State: {idleTimer.getCurrentState()}</Text>
</View>
);
}import {
IdleTimerProvider,
useIdleTimerContext,
} from "react-native-idle-timer-detection";
function App() {
return (
<IdleTimerProvider
timeout={120}
onIdle={() => {
// Show lock screen or logout
console.log("User idle - showing lock screen");
}}
onActive={() => {
// Hide lock screen
console.log("User active - hiding lock screen");
}}
>
<NavigationContainer>
<Stack.Navigator>{/* Your screens */}</Stack.Navigator>
</NavigationContainer>
</IdleTimerProvider>
);
}
// In any child component
function AnyScreen() {
const idleTimer = useIdleTimerContext();
const handleReset = () => {
idleTimer.reset();
};
return (
<View>
<Text>Remaining: {idleTimer.getRemainingTime()}s</Text>
<Button title="Reset Timer" onPress={handleReset} />
</View>
);
}The main hook for idle timer functionality.
interface UseIdleTimerProps {
/**
* Timeout duration in seconds before the timer goes idle.
* Default: 10 seconds
*/
timeout?: number;
/**
* Callback fired when the timer becomes idle (timeout reached).
* Fires once per idle cycle.
*/
onIdle?: () => void;
/**
* Callback fired when the user becomes active after being idle.
* Fires when transitioning from idle -> active state.
*/
onActive?: () => void;
/**
* Callback fired on every user action (touch, keyboard, etc.).
* Useful for tracking all user interactions.
*/
onAction?: () => void;
}interface IdleTimerHandle {
/** PanResponder instance for capturing touch events */
panResponder: PanResponderInstance;
/** Reset the timer and mark user as active */
reset: () => void;
/** Get the timestamp when the timer was started (in milliseconds) */
getStartTime: () => number;
/** Get the current timestamp (in milliseconds) */
getCurrentTime: () => number;
/** Get remaining time until idle (in seconds) */
getRemainingTime: () => number;
/** Pause the timer (optionally with a reason) */
pause: (reason?: string) => void;
/** Resume the timer (optionally with a reason) */
resume: (reason?: string) => void;
/** Check if the timer is currently idle */
getIsIdle: () => boolean;
/** Get the timestamp of the last reset (in milliseconds), or null if never reset */
getLastReset: () => number | null;
/** Get the timestamp of the last idle event (in milliseconds), or null if never idle */
getLastIdle: () => number | null;
/** Get the current state: "running" | "paused" | "idle" */
getCurrentState: () => "running" | "paused" | "idle";
}Context provider component that wraps your app and provides idle timer functionality to all child components.
Same as UseIdleTimerProps (see above).
Hook to access the idle timer from within a component wrapped by IdleTimerProvider.
Same as useIdleTimer return value (see IdleTimerHandle above).
Throws an error if used outside of IdleTimerProvider.
The library automatically handles:
- Touch Events: Uses
PanResponderto detect touch interactions and reset the timer - Keyboard Events: Automatically pauses when keyboard opens and resumes when it closes
- App State Changes: Automatically pauses when app goes to background and resumes when active
The timer can be in one of three states:
running: Timer is actively counting downpaused: Timer is paused (remaining time is preserved)idle: Timer has reached timeout and user is considered idle
- The timer uses deadline-based calculation for accurate remaining time
- When paused, the remaining time is preserved and resumes from the same point
- The
onIdlecallback fires only once per idle cycle - The
onActivecallback fires when transitioning from idle to active - The
onActioncallback fires on every user action (touch, reset, etc.) - All time values are in seconds except timestamp methods which return milliseconds
MIT