Learn how to detect if user is idle or inactive in Javascript.
There are many CPU intensive web apps which require constant attention like games, media players, etc. For these kinds of applications it becomes very important to switch the context when user is not interacting with the app or if it is in idle state.
This is one of the best thing to do, because if your application is running on mobile, then heavy CPU computation is draining lots of battery, so it is better to switch the context or stop the processing when user is inactive.
Detecting idle state in Javascript (React)
We will create a React component which will help us to detect the idle state. This component will take two callback functions onIdle and onActive which will be called based on the current state.
The way we are going to detect the inactivity is pretty straightforward.
In simple terms, an user is inactive if he is not interacting with app, which means user has not triggered any event from the keyboard, mouse, screen (touch and tap) for a period of time.
Thus we will use a timer using setTimeout which will start once user stops interaction with the app and reset this timer if user triggers any of the above event.
Also, we need to handle edge cases where the current window or tab is not focused, thus we will listen to the window events for blur
and focus
.
Base setup of React component.
This component will take three parameters,
1. Delay:- Time after which user will be considered inactive.
2. onIdle:- Callback function which will be invoked when user becomes inactive.
3. onActive:- Callback function which will be invoked when user becomes active.
As we are going to user the timers, we need a global object to store the timerId in order to reset it. useRef hook is the best fit for it.
And the timer should be started the moment component mounts and also cleanup should be done when the component unmounts, so we will also be using the useEffect hook.
Because we don’t need to render anything in this component, we will return null.
import { useEffect, useRef } from "react"; const IdleStateDetector = ({ delay, onIdle, onActive }) => { const timeoutId = useRef(); useEffect(() => { //do something on mount setup(); return () => { //do something on unmount cleanUp(); }; }, []); return null; } export default IdleStateDetector;
Listening the events.
We have to listen this list of events in-order to determine if user is inactive.
const setup = () => { document.addEventListener("mousemove", resetTimer, false); document.addEventListener("mousedown", resetTimer, false); document.addEventListener("keypress", resetTimer, false); document.addEventListener("DOMMouseScroll", resetTimer, false); document.addEventListener("mousewheel", resetTimer, false); document.addEventListener("touchmove", resetTimer, false); document.addEventListener("MSPointerMove", resetTimer, false); //edge case //if tab is changed or is out of focus window.addEventListener("blur", startTimer, false); window.addEventListener("focus", resetTimer, false); startTimer(); };
Timer functions
We will use two timer functions startTimer and resetTimer to start and reset the period of time for idle state.
const startTimer = () => { // wait till delay time before calling goInactive timeoutId.current = setTimeout(goInactive, delay); }; const resetTimer = () => { //reset the timer and make user active clearTimeout(timeoutId.current); goActive(); };
Now if user has not performed any activity in the specified time, then it means user is inactive, thus call the goInactive function in startTimer.
And vice versa if timer is reset, which means the user is active now, so we call the goActive function in resetTimer.
State change functions
Now inside this goActive and goInactive we should perform our activities like state update or calling the callback functions etc, depending upon the need.
const goInactive = () => { alert("User is idle"); onIdle && onIdle(); //optional if you want to start the idle detector again resetTimer(); }; const goActive = () => { // do something onActive && onActive(); startTimer(); };
Cleanup
When the component is about to unmount we want to remove all the event listeners and clear the timer to avoid memory leakage.
const cleanUp = () => { document.removeEventListener("mousemove", resetTimer); document.removeEventListener("mousedown", resetTimer); document.removeEventListener("keypress", resetTimer); document.removeEventListener("DOMMouseScroll", resetTimer); document.removeEventListener("mousewheel", resetTimer); document.removeEventListener("touchmove", resetTimer); document.removeEventListener("MSPointerMove", resetTimer); //edge case //if tab is changed or is out of focus window.removeEventListener("blur", startTimer); window.removeEventListener("focus", resetTimer); // memory leak clearTimeout(timeoutId.current); };
Complete code
import { useEffect, useRef } from "react"; const IdleStateDetector = ({ delay, onIdle, onActive }) => { const timeoutId = useRef(); useEffect(() => { setup(); return () => { cleanUp(); }; }, []); const setup = () => { document.addEventListener("mousemove", resetTimer, false); document.addEventListener("mousedown", resetTimer, false); document.addEventListener("keypress", resetTimer, false); document.addEventListener("DOMMouseScroll", resetTimer, false); document.addEventListener("mousewheel", resetTimer, false); document.addEventListener("touchmove", resetTimer, false); document.addEventListener("MSPointerMove", resetTimer, false); //edge case //if tab is changed or is out of focus window.addEventListener("blur", startTimer, false); window.addEventListener("focus", resetTimer, false); //start the timer startTimer(); }; const cleanUp = () => { document.removeEventListener("mousemove", resetTimer); document.removeEventListener("mousedown", resetTimer); document.removeEventListener("keypress", resetTimer); document.removeEventListener("DOMMouseScroll", resetTimer); document.removeEventListener("mousewheel", resetTimer); document.removeEventListener("touchmove", resetTimer); document.removeEventListener("MSPointerMove", resetTimer); //edge case //if tab is changed or is out of focus window.removeEventListener("blur", startTimer); window.removeEventListener("focus", resetTimer); // memory leak clearTimeout(timeoutId.current); }; const startTimer = () => { // wait till delay time before calling goInactive timeoutId.current = setTimeout(goInactive, delay); }; const resetTimer = () => { // reset the counter and make user go active clearTimeout(timeoutId.current); goActive(); }; const goInactive = () => { alert("User is idle"); onIdle && onIdle(); //optional if you want to start the idle detector again resetTimer(); }; const goActive = () => { // do something onActive && onActive(); startTimer(); }; return null; }; export default IdleStateDetector;
Please try it out yourself, code is available on github repo.
I hope you learned something today, if you think this would be helpful for others, please do share it. If you have any other approach let me know in the comment section.