Creating a "recently viewed pages" list can significantly enhance the user experience by providing easy navigation and a history of recently accessed content. Seeing how we are already using Rails, we are going to leverage Stimulus. Also, instead of storing the information on the server, we will use localStorage
to persist the viewed pages data across sessions.
Why Use Stimulus and localStorage
?
Usually for UI interactions, I prefer keeping things as lightweight as possible. This is why I prefer AlpineJS over Stimulus.
In this case, however, Stimulus is a good choice because there's a bit more logic in the JavaScript.
We will also use localStorage
as a client-side storage mechanism. It’s ideal for saving non-sensitive information like page history because the data is stored even after the user refreshes or closes the browser.
The downside to using localStorage
is that if the user logins from another computer, this is not synchronized. It is possible to sync this information with additional logic in the Stimulus controller, but if your use case is like mine, this is probably not necessary.
The Goal
Our goal is to build a recently viewed pages list that:
Tracks the pages visited by the user.
Stores metadata such as the page name and a short description.
Displays up to the last 20 recently visited pages.
Persists this data across sessions using
localStorage
.
The Stimulus Controller
Below is a Stimulus controller that handles tracking and storing the user’s visited pages:
// app/javascript/controllers/recently_viewed_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["currentPath"];
connect() {
if (this.currentPathTargets.length !== 0) {
let name = this.currentPathTarget.dataset.name;
this.saveCurrentPath(name);
}
}
saveCurrentPath(name) {
let path = window.location.pathname;
// Create an object with path, name, and description
let pathObject = {
path: path,
name: name
};
// Remove existing objects with the same path
let visitedPaths = this.removePathsContaining(path);
// Add the new object to the beginning of the array
visitedPaths.unshift(pathObject);
// Keep only the last 20 entries
if (visitedPaths.length > 20) {
visitedPaths = visitedPaths.slice(0, 20);
}
// Save the updated array back to localStorage
localStorage.setItem("visitedPath", JSON.stringify(visitedPaths));
}
removePathsContaining(substring) {
// Get the existing array from localStorage
let visitedPaths = JSON.parse(localStorage.getItem("visitedPath")) || [];
// Filter out objects where the path contains the substring
visitedPaths = visitedPaths.filter(
(item) => !item.path.includes(substring)
);
return visitedPaths;
}
resetPaths() {
// Clear the visitedPath item from localStorage
localStorage.removeItem("visitedPath");
}
}
To activate this controller and to track specific pages, you will need to add this to the body tag:
<body data-controller="smooth-scroll recently-viewed">
<div data-recently-viewed-target="currentPath" data-page-name="<%= @page_name %>"></div>
/* Your code */
</body>
You have the option of refactoring the above code or combining them. In my use case, I wanted to add the controller to the body tag to activate it, and then optionally have it save the page if I added the inner div
tag.
Using the list from localStorage
Now that you have the list, you can access it with something like this:
window.recentlyVisitedPaths = JSON.parse(localStorage.getItem("visitedPath")) || [];
The values will now be returned as JSON and you can render it using Stimulus or something similar.
Here is a demo of how I use it in my app at Proggy.io: