Session TimeOut in React
Before I start I would like to say I graduated!
Okay session timeout implementation. A session timeout is when some inactivity has been noticed after sometime on a webpage. This causes the your session to timeout , hence logging you out if you have not chosen to persist.I am sure most of us have encountered something like this :
This is a feature that has been implemented across many webpages that deal with sensitive user information. This article will guide us on how to implement this in our React Project.
Pre-requisites:
- Know HTML, CSS ,JS and React.
We will have a step by step from start to end so no worries if this is your first time. [okay you need to know some things]
- Node.js in your machine.
You can check by opening command prompt and typing : “node — v”. This is to check the version of node installed in your machine. If you have it , it should give you the version number as below. If nothing , check this.
1. Set up React App.
Open your terminal where you want your project and type in the following :
"npx create-react-app session-timeout"
This will instatiate a react app with the name session-timeout
After everything has installed and you see a 'happy coding' sentence in your terminal.
Type the following:
1. "cd session-timeout" [This is you going to inside your directory]
2. "npm start" [ this is the command used to start a react project]
The app will run and will open in your webpage with the link “http://localhost:3000”. And that’s it! we have a running react application!
2. Create components , set up libraries and packages.
We will implement a session timeout with a simple application that has two pages , Login and Homepage. Before we do that we need to install some few packages:
react-router-dom — This is responsible for dynamic routing in the web application.
react-idle-timer- This is a package that checks for inactivity in your application however you use it.
We can set up bootstrap for making the application beaurifuuul. but anyway. In your src(source) folder, create the following folders : components, hooks.
Components will hold our homepage and login page.
Hooks will hold our custom hook. timer thing. Now I just realized I don’t know alot about hooks.
Hooks — — crash course.
Hooks are reusable functions. If you have familiarized yourself with React I am sure you have seen things like useState, useEffect. Those are hooks. We can also have custom hooks. For them to be considered custom hooks the filename needs to be prefixed with the word “use”.
Example:
We will use the most basic activity in our daily lives: Walking. We use it to go from point A to B. Now if I was a creator ,ofcourse it would be boring and time consuming and wasting space writing the same function everywhere. I would create and place it somewhere and just call it anytime I need you to walk.
The gist describes how to create a custom hook:
Implementation of our custom hook useWalking:
Since we get the gist — [hihi] ,lets create a file useIdleTimer.js in our folder “Hooks” .This is where we will create a custom hook to check for inactivity in our application.
In your new created file , add this:
Let’s break it down:
- In line 2 , we have imported useIdleTimer from the package we installed.
-line 4 , we have defined our custom hook useIdle and imported new properties , onIdle, idleTime.
- line 5 we have created a state that checks and stores user idleness, can be true/ false.
- From line 8 to 28, we have defined a function (handleOnIdle) that is to be executed when we find out our user is idle.
const handleOnIdle = (event) => {
setIsIdle(true); // Set the state to true, indicating that the user is idle
const currentTime = new Date();// we get the current date time
const formattedCurrentTime = currentTime.toLocaleString("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
timeZoneName: "short",
});//we then format the date time to something readable
console.log("user is idle", event); // Log the message "user is idle" along with the event object
console.log("Last Active time", getLastActiveTime()); // Log the timestamp of the last user activity by calling getLastActiveTime() function
console.log("Current time", formattedCurrentTime); // Log the formatted current time for reference
onIdle(); // Call the onIdle function, it will be called in Homepage.js
};
// I have logged out last active time and current time for a reason we will see
-line 30 -34. The custom hook is destructuring the return values of the useIdleTimer hook in this section. The useIdleTimer hook accepts an object with the following properties as an argument:
timeout
: It specifies the duration (in milliseconds) after which the user is considered idle. It's calculated as1000 * 60 * idleTime
, whereidleTime
is a variable that determines the desired idle time in minutes.onIdle
: It is a callback function that will be executed when the user becomes idle. In the code,handleOnIdle
represents the function that will be called when the user becomes idle.debounce
: This is like an ‘uh’ before we give an answer. Like when someone asks you a question you “wait…” then you answer just as they are repeating the question.
It determines the debounce time (in milliseconds) for the idle detection. It means that the idle state won't be triggered immediately after the user becomes inactive, but only if they remain inactive for the specified debounce time. In the code, it's set to500
milliseconds which is 0.5 seconds.
line 35–41. We return all these properties we will use and export our hook. Ready for use.
React Router Dom
As I said , this is responsible for routing in our application. We have two pages, Login and Homepage. We want to make it so that when we login , it routes us to Homepage. On your App.js , put this :
import React from "react";
//this is where we have imported usable properties of react router dom.
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
// we have imported our pages from components from below.
import Login from "./Components/Login";
import HomePage from "./Components/HomePage";
function App() {
return (
<Router>
<Routes>
<Route exact path="/" element={<Login/>} />
<Route path="/home" element={<HomePage/>} />
</Routes>
</Router>
);
}
export default App;
//Inside a react app , there is usually Router (Like parent)
//It encapsulates different Routes.
//These routes are made up individual route(s).
//What we see up here is like this :
//you see our "http://localhost:3000/" - when our app runs we have directed to be
// the login page
//and "http://localhost:3000/home" - this will take us to our homepage.
//You will get an error cause we have not set up those pages . Let's fix that.
Login Page.
Paste it to a file “Login.js”
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./component.css";
function Login() {
const navigate = useNavigate();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
//function that takes us to home page with the endpoint "/home"
const handleLogin = (e) => {
e.preventDefault();
alert("Navigating to Home Page");
navigate("/home");
};
return (
<div className="login-container">
<h1>Login</h1>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="login-input"
/>
<br />
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="login-input"
/>
<br />
<button type="submit" className="login-button">Login</button>
</form>
</div>
);
}
export default Login;
// in our login page, we have form that inputs a username and password
// we also have a button that when clicked , we will utilize navigate and
// take us to our home-page with the route "/home"--- navigate("/home");
// this is the same link that we set in our app.js
// You can name it to how you want as long as they match , otherwise you will get a
// white screen since no link you declare exists
The login page from my end looks like this: has some styling
Notice my endpoint(localhost:3000) it is the same as the path we declared “/”
Click on the button and voila we go to home page. But we have no home page yet ,lets make that.
HomePage
Create a HomePage.js and add the following:
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import useIdle from "../Hooks/useIdleTimer.js";
import "./component.css"
function HomePage() {
const navigate = useNavigate();
const [showModal, setShowModal] = useState(false);
const [remainingTime, setRemainingTime] = useState(0);
const handleIdle = () => {
setShowModal(true); //show modal
setRemainingTime(30); //set 15 seconds as time remaining
};
const { isIdle } = useIdle({ onIdle: handleIdle, idleTime: 0.3});
useEffect(() => {
let interval;
if (isIdle && showModal) {
interval = setInterval(() => {
setRemainingTime(
(prevRemainingTime) =>
prevRemainingTime > 0 ? prevRemainingTime - 1 : 0 //reduces the second by 1
);
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [isIdle, showModal]);
useEffect(() => {
if (remainingTime === 0 && showModal) {
// alert("Time out!");
setShowModal(false);
navigate("/");
}
}, [remainingTime, showModal, navigate]); // this is responsoble for logging user out after timer is down to zero and they have not clicked anything
const handleLogOut = () => {
setShowModal(false);
navigate("/");
};
const handleStayLoggedIn = () => {
setShowModal(false);
};
function millisToMinutesAndSeconds(millis) {
var minutes = Math.floor(millis / 60000);
var seconds = ((millis % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}
return (
<>
{/* handle isIdle for the modal */}
{isIdle && showModal && (
<div className="modal">
<div className="modal-content">
<h2>Idle Timeout Warning</h2>
<p>You are about to be logged out due to inactivity.</p>
<br />
Time remaining: {millisToMinutesAndSeconds(remainingTime * 1000)}
<br />
<div className="row">
<button className="btn btn-danger" onClick={handleLogOut}>
Logout
</button>
<button className="btn btn-primary " onClick={handleStayLoggedIn}>
Stay Logged In
</button>
</div>
</div>
</div>
)}
<div>
<h1>Welcome to the Home Page</h1>
<p>Thank you for logging in!</p>
</div>
</>
);
}
export default HomePage;
Walk with me and let me explain the logic of this page.
the page should be like this : with your styling ofcourse
The moment we login , we are navigated this page(“/home”). see the search bar.
Now if we are inactive in this page after some time we will set , it triggers a popup and asks us if we want to stay logged in or we logout , meaning back to the login page.
It also automatically logs you out if you have not chosen any action after some time, (30 seconds).
Explaining code in snippets:
- Importation
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import useIdle from "../Hooks/useIdleTimer.js";
import "./component.css"
// we import the properties from react
// useNavigate from react router dom
// our custom hook useIdle from our file
// a css file
- Functions and declarations
const navigate = useNavigate();// we use it for navigation obvs
const [showModal, setShowModal] = useState(false);
const [remainingTime, setRemainingTime] = useState(0);
// we have the states for showModal and Remaining time
// showModal is responsible for displaying the modal if true/ false
//remaining time will show how many seconds we have before we are
//automatically logged out
//this function takes milliseconds and converts them to min and seconds.
function millisToMinutesAndSeconds(millis) {
var minutes = Math.floor(millis / 60000);
var seconds = ((millis % 60000) / 1000).toFixed(0);
return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}
- useIdle
Remember our custom hook useIdle? We are using it now. We first need to create a function handleIdle , this function is executed when our user is found idle.
That’s why we have to define it first before we use useIdle.
So our function handleIdle says when we are idle, we set our modal state to true, we also add remaining time to 30 seconds before our modal closes .
const handleIdle = () => {
setShowModal(true); //show modal
setRemainingTime(30); //set 30 seconds as time remaining
};
We then implement this function like this :
const { isIdle } = useIdle({ onIdle: handleIdle, idleTime: 0.3});
console.log("is user idle?", isIdle);// null, true, false
The useIdle
hook is called with an object as an argument, containing two properties:
onIdle
: It is a callback function (handleIdle
) that will be executed when the user becomes idle. [the function just up here]idleTime
: It specifies the duration (in minutes) after which the user is considered idle. In this case, it's set to0.3
minutes (or 18 seconds)- The
useIdle
hook returns an object that is destructured to obtain theisIdle
value.isIdle
represents the current idle state of the user. - The
console.log
statement is used to log the message"is user idle?"
along with the value ofisIdle
. This will output the current idle state of the user to the console. It can be true, false , null.
What we are dealing with is what happens when its true.
useEffects
1. Timer useEffect
useEffect(() => {
let interval;
if (isIdle && showModal) {
interval = setInterval(() => {
setRemainingTime(
(prevRemainingTime) =>
prevRemainingTime > 0 ? prevRemainingTime - 1 : 0 //reduces the second by 1
);
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [isIdle, showModal]);
This code sets up an interval to update the remainingTime
state every second when both isIdle
and showModal
are true
.
Now this setInterval
is supposed to take the current/previous time(setRemainingTime
) value and if the time is greater than 0 , then we minus 1 second else we set it to 0 since its not greater than 1.
The interval is set to trigger every 1000 milliseconds (1 second) since the second argument of setInterval
is 1000
.
When the component is unmounted or when isIdle
or showModal
change, the interval is cleared to prevent any further updates.
2. Logout useEffect
useEffect(() => {
if (remainingTime === 0 && showModal) {
//alert("Time out!");
setShowModal(false);//close the modal
navigate("/");//go to login page
}
}, [remainingTime, showModal, navigate]);// this function will only run
//considering the remaining time , show modal and navigate , though I don't
//its necessary.
This useEffect basically says if our remaining time is completely equal to 0 and our modal is still up , then close the modal and go to login page. Meaning you are logged out.
- Handle Functions
const handleLogOut = () => {
setShowModal(false);
navigate("/");// go to login page
};
const handleStayLoggedIn = () => {
setShowModal(false);//close the modal
};
handleLogout — just closes the modal and navigates you back to the login page.
handleStayLoggedIn — This closes the modal , you can continue with what you were doing. Our custom hook will be triggered if it notices any idleness, and start the process again.
- Return
<>
{/* handle isIdle for the modal */}
{isIdle && showModal && (
<div className="modal">
<div className="modal-content">
<h2>Idle Timeout Warning</h2>
<p>You are about to be logged out due to inactivity.</p>
<br />
Time remaining: {millisToMinutesAndSeconds(remainingTime * 1000)}
<br />
<div className="row">
<button className="btn btn-danger" onClick={handleLogOut}>
Logout
</button>
<button className="btn btn-primary " onClick={handleStayLoggedIn}>
Stay Logged In
</button>
</div>
</div>
</div>
)}
<div>
<h1>Welcome to the Home Page</h1>
<p>Thank you for logging in!</p>
</div>
</>
Conditional rendering. We tell our application only show this popup if both showModal and isIdle are true. BOTH not either or , BOTH.
SEE what we just did ! After logging in ,and we do nothing for 18seconds(0.3)[idleTme] and with an “uh” -debounce of 0.5 seconds(500).
So the application is extremely sure you were doing nothing.
This pops up.
It then gives you a countdown of 30 seconds to decide what you want to do , stay logged in or logged out.
If the countdown is up and you decided not to choose.It will choose for you and boot you out. You can adjust the remainingTime and idleTime to your liking.
The link to this github project is here.
That’s it! You have implemented session timeout.