How to detect inactive user to auto logout by using idle timeout in JavaScript, React, Angular and more?

Imagine you have an application in which users have some sensitive data and they may forget to logout, leave the session open and someone else can extract these sensitive data like a banking account, personal information, private messages,…
As a developer, you need to have a solution to protect our users when they are leaving for a while to drink water, to have a phone call, or to be in the restroom,…
This post is not only for React developers but also for others who are using other JavaScript frameworks because we will be focusing on problem solving with pure JavaScript rather than using a specific framework.
Let’s get started!
How to detect inactive users?
Inactive users means they don’t interact with our application for a while when they are leaving or switching to another browser tab. From a technical perspective, it should be tracking activity by mouse move, mouse click, scroll, or keyboard input.
How to implement an idle timer for inactive users?
When we hear about the timeout, I’m pretty sure that you will think about using setTimeout method in this context. And yes, it’s the simplest way to implement the timeout on the browser by the following steps:
- Call
setTimeout(<logoutMethod>, <timeoutInMiliseconds>) - If users do something on the app (move, click, input), we clear the timeout by using
clearTimeoutmethod and then callsetTimeoutagain.
However, this solution is just working fine if the users are on a single tab. If they are using our app on multiple tabs, it should be an issue since they are active on the current tab but inactive on the rest. Then we need to find another solution that allows the active state to sync across multiple tabs.
Implement an idle timer on multiple tabs
Take a look at the diagram first:

There are 3 things that I need to clarify:
- localStorage: we need to share the active state across the tabs so we will use it.
- Set new expired time flow: when the users interact with our app, we consider they are active and need to re-calculate the new expired time for them. It should be: <current time> + <timeout time>
- Interval tracker: We will create an interval to run every amount of time (eg: 1 second) to check the Expired time in localStorage. If the current time is over the expired time, we will handle the timeout action to clear the current session (eg: Auto logout)
Let me translate the diagram to JavaScript point of view.

As you can see, the actual timeout is in localStorage instead of the application tabs so the timeout will happen for all tabs when we reach the expired time.
I think you are able to understand the concept. Let’s get our hands dirty with the coding implementation.
Step 1: Create user interaction handler
In this post, we will track only 3 events: mouse move, mouse click, and keyboard event.
Create IdleTimer class with our following code. We will have a full demo code at the end of this post so just read the code first.

Explain:
- Line 9–11, the updateExpiredTime(): We calculate new expired time then store to _expiredTime in localStorage. We have the value of
this.timeoutfrom the constructor. - Line 13–17, the tracker(): we add the event listeners to track user interaction with mousemove, scroll, and keydown event.
- Line 5, the binding handler: If you’re not familiar with this syntax, please take a look at this answer to understand. Because the updateExpiredTime() method needs to access
this.timeout(line 10) so the.bind(this)will keep the scope in the class scope.
Step 2: Add the interval to track timeout
Now, we need to add the interval to track expired time in localStorage. Let’s update our IdleTimer class as the following code:

Explain:
- Line 11: At the start point, we need to generate our first expired time.
- Line 13–18: The interval logic to track every 1000 milliseconds (1 second)
- Line 14: Get the expiredTime from localStorage. If it doesn’t exist in the storage, then expiredTime is assigned to 0. It’s possible null because 1 browser tab is expired, the storage will be cleared.
- Line 15–17: To track if the current time is over the expired time.
Step 3: Add the timeout callback
I think you’re still confused after 2 above steps because it’s just a single class. How we expose this class for the others to use? This step will give you the answer. We need to inject a callback from outside of our class. Take a look at the updates:

And this is how we use this class from outside. I’m using React for this demo so you can apply in a similar way.
Create your App.js as below:

I’m assuming you have a basic understanding of React hooks (useEffect, useState) so I’ll just explain the logic instead.
Explain:
- Line 7–11: We create IdleTimer instance with
timeout=10and theonTimeoutcallback to update the value ofisTimeout - Line 15: We just add a simple render here. If the user is inactive in 10 seconds, we will display
Timeouttext. Otherwise, we displayActive. In a real use case, we can render a component instead or even redirect users to the login page.
Step 4: Clean up
We’re almost done with the previous step. Why do we need to clean up?
There are 2 things that we need to clean up in the IdleTimer.
- After the timeout is reached, we need to stop the interval or it will keep looping forever.
- We also need to remove the listeners for mouse move, mouse click and keyboard input since we don’t need to track these events anymore.
We add the cleanUp method to IdleTimer class like this:

And then we update the interval logic to clean up when reaching the timeout

We also need to refactor App.js for the useEffect hook

1 more step: Improve the user interaction tracker
If you take a look at our tracker(), you will see that we re-calculate the new expired time every time users move their cursor so it’s not really good from a performance point of view. We can improve a little bit by adding the delay as the following diagram:

You can see that we will have 1 new thing: The timeout tracker. For example, when users interact, we create a timeout tracker. If they stop interact for a short period (eg: 300 milliseconds), the timeout track is reached then we store the expired time to localStorage. If they keep moving the cursor in that 300 milliseconds period, we will clear the old timeout tracker and create the new one. Let’s see how we can implement it by the following code:

The last improvement
What if the users close the window and then they open the app again?
We need to check the expired time in the initial state. There are 2 scenarios here.
- If they are still in the active time: Our solution can still handle by creating the new expired time. No problem.
- If they open the app after the expired time, we need to immediately trigger the
onTimeOutinstead.
Look at the updates:

Explain:
- Line 6–10: we check the the initial state if we have the expiredTime value in localStorage, we immediately call onExpired callback and then stop the execution.
We also need to add 1 more line:

As you can see we have the line number 47 to remove the expiredTime from localStorage to make sure that the next initial state (probably users will log in once again) will generate the new expired time.
Then we need to update the App.js:

Why do we have 2 separated callbacks here? You can name whatever you want but the purpose is:
- onTimeout: The callback will be triggered if the users are in the app and have the idle timeout.
- onExpired: The callback will be triggered if the users re-open the app after the expired time.
Finally, we’re done for the idle timer across multiple tabs.
Here is the full demo on Codesandbox:
https://codesandbox.io/embed/sleepy-euler-kw4m0?fontsize=14&hidenavigation=1&theme=dark






