Timeout 2023: Implementing App Timeout in React

Β·

7 min read

Introduction

What a year 2023 has been, it feels just like yesterday I could hear loud fireworks and excitement celebrating what was then a new year. Fast forward to now, a lot has changed, I've experienced immense professional growth this year.

I started writing technical articles such as this, which improved both my technical skills and my writing skills exponentially. I completed my Master's degree in Computer Science this year, explored opportunities to contribute to different projects, and greatly improved my Software Engineering skills. Got a great recommendation and now working on a project I'm passionate about.

I'm grateful for all the ups and downs I've experienced all year long. Here's to a more fulfilling 2024 ahead! πŸ₯‚

In this article, I share a straightforward approach to implementing timeout functionality for a React app by creating a provider and wrapping the entire app with it.

Prerequisites

It is important to have a good knowledge of Javascript, React, and Vite to fully enjoy the sweets in this piece.

Do I really need a timeout?

Most web applications may not need a timeout functionality, however, if you are building a project that handles sensitive user details or a financial application where users perform transactions and leave their money in your care, then it is extremely important to have some sort of timeout functionality that monitors user activity and triggers some action when a set time elapses. Some advantages of this include;

  • Security and Privacy Enhancement - The possibility of illegal access to a user's account is decreased with the aid of timeouts. An automatic timeout makes sure that the session ends after a set amount of time in the event that the user forgets to log out or leaves the app unattended.

  • Resource Management - The timeout feature can be used to release and free server resources linked with a user session. This is especially significant in cases when resources are limited, and it is critical to efficiently manage and release resources when they are no longer required.

  • Compliance with Security Standards - Many security standards and best practices propose using timeouts to prevent session hijacking and illegal access. Adherence to these standards contributes to the application's security and compliance.

Now that we've established the importance of having timeout functionality in an app, let's go ahead and implement it.

Implementing Timeout in React.

We start by bootstrapping our React app using Vite. Navigate to your preferred directory and run the following commands to set up the app.

npm create vite@latest
// select Typescript as the preferred language, and
// specify the project name, here it is app-timeout 

cd app-timeout

npm install

npm run dev

We also need to install react-router-dom and @types/node, to help with routing and provide the appropriate types we need respectively. Run the code below to do this:

npm i react-router-dom 
npm install --save-dev @types/node

In the src folder, create a new folder named components, in there create another folder called providers and in there create a file and name it timeout-provider.tsx. In this file, add the following code:

import { ReactNode, useEffect, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';

export function TimeoutProvider({ children }: { children: ReactNode }) {
  const navigate = useNavigate();
  const location = useLocation();
  const timeoutRef = useRef<NodeJS.Timeout>();

  useEffect(() => {
    if (exemptedRoutes.includes(location.pathname)) return;
    const handleWindowEvents = () => {
      clearTimeout(timeoutRef.current);

      timeoutRef.current = setTimeout(() => {
        // perform whatever logic you want in here, clear localStorage and log user out, show a popup modal or just navigate to another page
        navigate('/2024');
      }, 10000);
    };

    // listen for specific window events to ensure user is still active
    window.addEventListener('mousemove', handleWindowEvents);
    window.addEventListener('keydown', handleWindowEvents);
    window.addEventListener('click', handleWindowEvents);
    window.addEventListener('scroll', handleWindowEvents);

    handleWindowEvents();

    //cleanup function
    return () => {
      window.removeEventListener('mousemove', handleWindowEvents);
      window.removeEventListener('keydown', handleWindowEvents);
      window.removeEventListener('click', handleWindowEvents);
      window.removeEventListener('scroll', handleWindowEvents);
    };
  }, [navigate, location.pathname]);

  return children;
}

const exemptedRoutes = ['/404', '/sign-up', '/forgot-password'];

Getting straight to business, the above code is the main functionality of this feature, here we begin by importing the necessary hooks we will need from react and react-router-dom which we've installed to help manage routing in our react app. We then created our provider function called TimeoutProvider and exported it.

We initialized the TimeoutProvider function to expect children of type ReactNode which is essentially any node that could be rendered in React (e.g., JSX, strings, numbers, fragments, etc.). Then we initialized navigate, location and timeoutRef variables with the appropriate hooks, navigate is used from react-router-dom to route between different pages in our react app, location to track the current location path, and timeoutRef used to track our timeout element.

The entire basis of our approach is to listen to some events and if a user stops performing all of these events, the user is deemed idle and the timeout is triggered after a set period of time.

We achieve this by using a useEffect, we made provisions for some routes to be exempted, for example, there's no point triggering timeout when a user is trying to sign up on your app or when they are trying to reset their password, however, there may be other scenarios where it will be appropriate to exempt some other routes too as you see fit.

A function handleWindowEvents is then created to handle what happens when none of the events we're listening for has been performed, and trigger the timeout after the set period. We essentially clear any existing timeout and then set a new timeout. A time of 10000ms (10 seconds) is used for the purpose of this article, ideally, this value might come from a Backend service.

If none of the events are triggered and the time runs out, then we can perform whatever logic we want, in our case, we simply usher ourselves into the year 2024 by navigating to /2024 page.

When the useEffect renders, we listen for the mousemove, keydown, click and scroll events, these are the events that tell if a user is active on a page or not, and we run the handleWindowEvents function whenever any of these events get triggered. We also make sure to call the handleWindowEvents on the first render of the useEffect.

We perform a cleanup function when the component unmounts by removing all the event listeners, passed navigate and location.pathname into the dependency array and return children. We also initialize and specify some routes we want to be exempted.

Update the code in the main.tsx file to the code below:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';

import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { Home } from './pages/Home.tsx';
import { NextYear } from './pages/2024.tsx';

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: '/',
        element: <Home />,
      },
      {
        path: '/2024',
        element: <NextYear />,
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Here we create our router and map each route to its corresponding component.

In the src folder, create a new folder called pages and in there create two new files called Home.tsx and 2024.tsx

In Home.tsx put the following code:

export function Home() {
  return (
    <div>
      <p style={{ fontSize: '2rem', fontWeight: 'bold' }}>Adios 2023, thanks for a great time!🫑</p>
    </div>
  );
}

In 2024.tsx put the following code:

export function NextYear() {
  return (
    <div>
      <p style={{ fontSize: '2rem', fontWeight: 'bold' }}>Welcome to 2024!!!πŸŽ‰πŸΎπŸŽŠ</p>
    </div>
  );
}

Finally, update the code in App.tsx to the following:

import './App.css';
import { Outlet } from 'react-router-dom';
import { TimeoutProvider } from './components/providers/timeout-provider';

function App() {
  return (
    <TimeoutProvider>
      <Outlet />
    </TimeoutProvider>
  );
}

export default App;

By wrapping Outlet with the TimeoutProvider we created, we've made sure that our timeout functionality is available to every page in the react app apart from the exempted ones. Hence this achieves the goal of implementing a timeout for a react application.

Conclusion

What a special way for 2023 to "timeout" πŸ˜‰. There may be other approaches to implementing this functionality, if you know another cool way, please leave a comment and share. I hope this article has been of great help to you.

I look forward to an amazing 2024, and getting to share more valuable content as I progress in this life and career journey.

Here is the Github Repo link to the code sample used in this article.

Cheers to a happy new year in advance!!! πŸ₯‚

Β