Skip to content

Instantly share code, notes, and snippets.

@Bonny-kato
Last active September 1, 2023 04:25
Show Gist options
  • Save Bonny-kato/ae0213f0acbbb4c59fcf9d7352a31441 to your computer and use it in GitHub Desktop.
Save Bonny-kato/ae0213f0acbbb4c59fcf9d7352a31441 to your computer and use it in GitHub Desktop.
React hook called `useIdleTimer` that tracks user activity and determines if the user is currently inactive. It takes in several configuration options as arguments and returns an object containing a boolean value indicating whether the user is currently inactive.

React Hook for Tracking User Inactivity

Preview:
import {
    getValueFromLocalStorage,
    localStorageKeys,
    removeValuesFromLocalStorage,
    saveValueToLocalStorage,
} from "@/utils/local-storage";
import { TFunction } from "@/types";
import { useEffect, useRef, useState } from "react";

interface UseIdleTimer {
    debounce?: number; //  Delay until function calls after user becomes inactive
    onIdle?: TFunction; // Function to call when a user becomes inactive
    onActive?: TFunction; // Function to call when a user becomes active
    TTL?: number; // Time to live, number of minutes after which user will be considered inactive
}

/**
 * `useIdleTimer` is a React hook to track user activity and time of inactivity.
 * It provides several configuration options as arguments and returns a boolean indicating whether the user is currently inactive.

 * returns object containing boolean indicating whether the user is currently inactive
 */
const useIdleTimer = ({
    onIdle,
    onActive,
    debounce,
    TTL = 2,
}: UseIdleTimer) => {
    const [isIdle, setIsIdle] = useState(false);
    const intervalRef = useRef<NodeJS.Timeout | null>(null);
    const eventsToListen = ["click", "mousemove", "keypress", "scroll"];

    const clearLastActivityTime = () => {
        removeValuesFromLocalStorage(localStorageKeys.LAST_ACTIVITY_TIME);
    };

    // Checks for user inactivity based on last activity timestamp
    const checkForUserInactivity = () => {
        const currentTime = Date.now();
        // Fetch last activity timestamp from localstorage
        const lastActivityTime = parseInt(
            getValueFromLocalStorage(localStorageKeys.LAST_ACTIVITY_TIME, "0")
        );

        // Calculate difference in minutes
        const diffInMs: number = currentTime - lastActivityTime;
        const diffInMinutes: number = diffInMs / (1000 * 60);

        // Check if the difference in minutes is bigger or equal to TTL
        if (diffInMinutes >= TTL) {
            clearLastActivityTime();
            if (onIdle) {
                onIdle();
                setIsIdle(true);
            }
        }
    };

    const checkLastActivityTimeExistence = (): boolean => {
        const lastActivityTime = getValueFromLocalStorage(
            localStorageKeys.LAST_ACTIVITY_TIME
        );
        return !!lastActivityTime;
    };

    /**
     * Updates last activity timestamp in localstorage and executes the provided
     * onActive function whenever a registered event occurs
     */
    const updateLastActivityTime = () => {
        saveValueToLocalStorage(
            localStorageKeys.LAST_ACTIVITY_TIME,
            `${Date.now()}`
        );
        if (onActive) {
            onActive();
        }
    };

    const startTrackingUserActivity = () => {
        eventsToListen.forEach((event) => {
            window.addEventListener(event, updateLastActivityTime);
        });
    };

    const stopTrackingUserActivity = () => {
        eventsToListen.forEach((event) => {
            window.removeEventListener(event, updateLastActivityTime);
        });
        clearLastActivityTime();
    };

    // Start an interval that checks for user inactivity every debouncing millisecond
    useEffect(() => {
        intervalRef.current = setInterval(() => {
            checkForUserInactivity();
        }, debounce ?? 30000);

        // Clean up the interval on component unmount
        return () => {
            if (intervalRef.current) {
                clearInterval(intervalRef.current);
            }
        };
    }, []);

    // If a user is already idle, stop tracking user activity
    useEffect(() => {
        if (isIdle) {
            if (intervalRef.current) {
                clearInterval(intervalRef.current);
            }
        }
    }, [isIdle]);

    /**
     * This effect is triggered on component mount.
     * It checks if there is a recorded "last active time" in the local storage.
     * If it exists, the function `checkForUserInactivity` is called to determine the duration of the user's inactivity.
     * */

    useEffect(() => {
        if (checkLastActivityTimeExistence()) {
            checkForUserInactivity();
        }
    }, []);

    // Sets up the user activity tracking
    useEffect(() => {
        updateLastActivityTime();
        startTrackingUserActivity();

        // Clean up the listeners on component unmount
        return () => {
            stopTrackingUserActivity();
        };
    }, []);

    return { isIdle };
};
export default useIdleTimer;



// example on how to use it
useIdleTimer({
    onIdle: signOut,
    debounce: 10000,
    TTL: 0.5,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment