Detect user activity with a custom useIdle React Hook

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet

If the user hasn't used your application for a few minutes, you may want to log them out of the application automatically in case they've stepped away from the machine and someone nefarious attempts to use their session. Let's checkout how you can create a custom React hook that wraps a regular npm module called activity-detector to solve this problem.

Instructor: [00:00] All right, we've got this simple little app that is rendering, "Are you still there?" or, "Hello, there!" based off of this isIdle state. We've set it to true. It's going to ask, "Are you still there?" If we set it to false, then it will say, "Hello, there!"

[00:12] We want to make this dynamic based off of whether the user has been interacting with the app over a period of time. There's a module we can use for this called activity-detector. We're going to import the default createActivityDetector. Then we can use that to determine whether or not the user is active.

[00:31] Let's go ahead and make a useIdle function. That will take the options that we'll pass to the activity-detector. We'll make an isIdle state and a setIsIdle from react.useState. We'll initialize that to false. Then I'm going to make a react.useEffect. This we only want to have run onMount.

[00:56] We don't need it to be rerun every single render, so we'll make an empty list of dependencies there. Then we'll return the isIdle state.

[01:05] In our app, we can say useIdle. Now we just need to fill in this useEffect. When the component is mounted, we're going to create a new activityDetector. I'll say, const activityDetector = create activityDetector. We're going to pass along those options, so that this is configurable from the outside.

[01:26] Then we're going to say, activityDetector.on('idle'), so when the user is idle, we'll set isIdle to true. We'll do the opposite for when the user is active. We'll set isIdle to false.

[01:40] Then when the component is unmounted, we need to make sure we remove all the event handlers that the activityDetector has set on our body. We'll return a cleanup function that simply calls activityDetector.stop. With that, our activityDetector is waiting for a certain amount of time before it says that the user is idle. Let's speed that process up a little bit.

[02:03] I'm going to say timeToIdle as one of the options, we'll say 1000. That option will get passed on to the create activityDetector, which uses that to determine how long to wait before calling our idle callback.

[02:17] Here, it says, "Are you still there?" Now, if I interact with that, it will go back to, "Hello, there!" so it knows that I am not idle. Then after one second, this timeToIdle that I set will go back to "Are you still there?" We can go back and forth, and after a second it will ask me again if I'm still there.

[02:36] Perfect. This works great for if the user is idle and you want to automatically log them out after a certain amount of time, or something like that.

[02:44] In review, what we have is this isIdle state that we're getting from useIdle. We're passing along all the options that we want to for the activityDetector, which is the abstraction we're using to determine the user's current activity. Then in this useIdle, we're going to create that isIdle and a setIsIdle state based off of react.useState.

[03:05] Then we'll have a react.useEffect where we interact directly with this activityDetector that we create based off of the options that the user passes to us. We're going to set the isIdle state whenever that activityDetector tells us that the user is idle. We'll set it again if the user changes to active again.

[03:24] Then, of course, we want to make sure we avoid memory leaks by telling the activityDetector to remove all event handlers that it set up to detect the user's activity. We also pass an empty array of dependencies for this effect, so that it's only run on the mount of the component and not run every rerender of our component.