I need to detect the idle time and automatically show a dialog that will say "You've been inactive for a while. To keep your session active, please click the button below"
When he clicks that button, it would reset the time so it wouldn't call the logout. Right now its still calling it.
However it doesn't work well. Is there a better approach or recommended way to handling idle time in my app or is this ok?
Here is the codesandbox CLICK HERE
import { Button, Dialog } from "@mui/material";
import DialogHeader from "./components/DialogHeader";
import DialogBody from "./components/DialogBody";
import DialogFooter from "./components/DialogFooter";
import { useIdleSession } from "./hooks/useIdleSession";
function App() {
const handleLogout = () => {
console.log("logout");
};
const IDLE_CONFIG = {
threshold: 5000, // idle time threshold in milliseconds
timeout: 8000, // timeout duration in milliseconds
action: handleLogout // action to be taken when timeout is reached
};
const { open, idleTime, handleUserActivity } = useIdleSession(IDLE_CONFIG);
return (
<>
<h1>HELLO WORLD</h1>
<Dialog open={open}>
<DialogHeader>
{" "}
You've been idle for {idleTime / 1000} seconds
</DialogHeader>
<DialogBody>
You've been inactive for a while. To keep your session active,
please click the button below
</DialogBody>
<DialogFooter>
<Button
color="primary"
variant="contained"
onClick={handleUserActivity}
>
Keep Session Active
</Button>
</DialogFooter>
</Dialog>
</>
);
}
export default App;
useIdleSession
import { useEffect, useState, useCallback } from "react";
export const useIdleSession = (config) => {
const [lastActive, setLastActive] = useState(new Date());
const [open, setOpen] = useState(false);
const [idleTime, setIdleTime] = useState(0);
const [logoutIntervalId, setLogoutIntervalId] = useState(null);
const isLoggedIn = true;
//function to handle user activity
const handleUserActivity = useCallback(() => {
setLastActive(new Date());
setOpen(false);
clearTimeout(logoutIntervalId);
}, [logoutIntervalId]);
// useEffect to check for user activity every minute
useEffect(() => {
if (isLoggedIn) {
// setInterval to check for user activity
const intervalId = setInterval(() => {
// get current time
const currentTime = new Date();
// calculate idle time
const idle = currentTime - lastActive;
// check if user has been idle for more than the threshold time
if (idle > config.threshold) {
setIdleTime(idle);
setOpen(true);
// set a timeout to run the action if they do not click the "Keep Session Active" button
const newLogoutIntervalId = setTimeout(config.action, config.timeout);
setLogoutIntervalId(newLogoutIntervalId);
}
}, config.threshold);
// cleanup function to clear interval when component unmounts
// document.addEventListener('mousemove', handleUserActivity);
// document.addEventListener('keypress', handleUserActivity);
return () => {
clearInterval(intervalId);
// document.removeEventListener('mousemove', handleUserActivity);
// document.removeEventListener('keypress', handleUserActivity);
};
}
}, [isLoggedIn, lastActive, handleUserActivity, config]);
return { open, idleTime };
};
The useIdleSession
doesn't return any handleUserActivity
property, e.g. return { open, idleTime, handleUserActivity };
. handleUserActivity
is undefined in calling components.
Return the declared handleUserActivity
callback function so it can be destructured from the hook. I recommend also storing the timer/interval id in React refs so they can be used to also correctly clear any running timers on the event of component unmounting.
export const useIdleSession = (config) => {
const [lastActive, setLastActive] = useState(new Date());
const [open, setOpen] = useState(false);
const [idleTime, setIdleTime] = useState(0);
const intervalId = useRef();
const logoutIntervalId = useRef();
const isLoggedIn = true;
// function to handle user activity
const handleUserActivity = useCallback(() => {
setLastActive(new Date());
setOpen(false);
clearTimeout(logoutIntervalId.current);
}, []);
// Effect to return cleanup function to clear any running timers upon
// component unmount.
useEffect(() => {
return () => {
clearInterval(intervalId.current);
clearTimeout(logoutIntervalId.current);
};
}, []);
// Effect to check for user activity periodically
useEffect(() => {
if (isLoggedIn) {
// setInterval to check for user activity
intervalId.current = setInterval(() => {
// get current time
const currentTime = new Date();
// calculate idle time
const idle = currentTime - lastActive;
// check if user has been idle for more than the threshold time
if (idle > config.threshold) {
setIdleTime(idle);
setOpen(true);
// set a timeout to run the action if they do not click the "Keep Session Active" button
logoutIntervalId.current = setTimeout(config.action, config.timeout);
}
}, config.threshold);
return () => {
clearInterval(intervalId.current);
};
}
}, [isLoggedIn, lastActive, handleUserActivity, config]);
return { open, idleTime, handleUserActivity };
};
The useIdleSession
hook logic can be simplified a bit. There's no need really to store a datestamp as the basic gist is to just instantiate a timeout that expires after the specified idle timeout, and upon any interactive event by a user to clear the previous timer and reinstantiate. It only needs to return a function to the component to manually reset, if/when necessary.
Example:
export const useIdleSession = ({ timeout, onExpire }) => {
const timerId = useRef();
//function to handle user activity
const handleUserActivity = useCallback(() => {
clearTimeout(timerId.current);
timerId.current = setTimeout(onExpire, timeout);
}, [onExpire, timeout]);
useEffect(() => {
if (isLoggedIn) {
// Listen for user activity
document.addEventListener("mousemove", handleUserActivity, {
passive: true
});
document.addEventListener("mousedown", handleUserActivity);
document.addEventListener("keydown", handleUserActivity);
document.addEventListener("touchstart", handleUserActivity);
handleUserActivity();
return () => {
document.removeEventListener("mousemove", handleUserActivity, {
passive: true
});
document.removeEventListener("mousedown", handleUserActivity);
document.removeEventListener("keydown", handleUserActivity);
document.removeEventListener("touchstart", handleUserActivity);
};
}
}, [ handleUserActivity]);
useEffect(() => {
return () => {
clearTimeout(timerId.current);
};
}, []);
return { reset: handleUserActivity };
};