The biggest project I work on at work is a React-based Web App (that ISN’T a Single Page Application). At its core it is a directory listing for people on a site which are represented by custom post types with associated custom taxonomies. We’ve tried to build it to be as flexible and customizable as possible but this has led to some sites having many options for filtering a rather large listing of people. Additionally the plugin has the ability to insert different pre-filtered lists on different pages of the site.
The latest major version of the plugin introduced the ability to save the state of filters and pagination using the browser’s sessionStorage
so that if a visitor had some complex filters set or were multiple pages into a directory they wouldn’t be back to the defaults if the clicked the back button after visiting a person’s page.
The initial code worked well enough but as with almost any piece of software, the more people used and tweaked it the more edge cases appeared.
The first such bug occurred when a user clicked through to a person’s page and then, instead of using the back button, used another method (e.g. the navigation menu or typing in the URL) to go to a different directory on the same site. The plugin would attempt to apply any existing filters as well as load the appropriate page on the new list. The initial fix to this was to add the URL to the sessionStorage
which solved the problem of clicking on a different directory page, but the bug still persisted if you didn’t use the back button to navigate back to the directory page (e.g. you used the nav menu).
The idea that loading the existing filters when a user used this navigation method was briefly considered then dismissed as a bug that we explained away by saying “its a feature not a bug”. So it was back to the drawing board.
Enter the History API
Before this the only thing I remembered about the History API was the great fear when it was introduced that sites would be able to see all of your history and do nefarious things with that (I feel like this was like mid 2000’s? 🤷). When I wanted to actually use the API for that “nefarious” purpose I was sorely disappointed to learn that is not how it actually works.
It turns out that the History API doesn’t automatically record your browser history, but you can use it to move through the history. It does allow you to manually create your own history record that you CAN use to see where you’ve been.
Into the weeds…
The initial code to load and clear the state looked like this:
let savedOptions = sessionStorage.getItem( 'directorySavedValues' );
if ( savedOptions !== null ) {
savedOptions = JSON.parse( savedOptions );
initialCurrentPage = savedOptions.currentPage;
initialTaxFilter = savedOptions.taxFilter;
initialAlphaFilter = savedOptions.alphaFilter;
}
sessionStorage.removeItem( 'directorySavedValues' );
and when a user clicked on a link to a profile page:
const writeSessionStorage = () => {
sessionStorage.setItem(
'directorySavedValues',
JSON.stringify( {
currentPage,
taxFilter,
alphaFilter,
} );
);
}
<a
className="card-link"
rel="bookmark"
onClick={ () => {
props.storageCallback();
} }
href={ linkURL }
>
Then after the first fix
(New code is in bold)
let savedOptions = sessionStorage.getItem( 'directorySavedValues' );
if ( savedOptions !== null ) {
savedOptions = JSON.parse( savedOptions );
}
if (
savedOptions !== null &&
window.location.href === savedOptions.pageURL
) {
initialCurrentPage = savedOptions.currentPage;
initialTaxFilter = savedOptions.taxFilter;
initialAlphaFilter = savedOptions.alphaFilter;
}
sessionStorage.removeItem( 'directorySavedValues' );
const writeSessionStorage = () => {
const pageURL = window.location.href;
sessionStorage.setItem(
'directorySavedValues',
JSON.stringify( {
pageURL,
currentPage,
taxFilter,
alphaFilter,
} );
);
}
<a
className="card-link"
rel="bookmark"
onClick={ () => {
props.storageCallback();
} }
href={ linkURL }
>
The History API to the rescue
After much Googling and Stack Overflow reading (like any good developer) I hadn’t found the answer to my problem, I hadn’t even found a solution that was close enough I could adapt it to solve my problem. So I opened up the MDN entry for the History API and my browser’s console and started experimenting.
What I eventually discovered was that I could accomplish my goal using the following steps:
- Replace
sessionStorage.setItem
withhistory.pushState
to store the data - Replace
sessionStorage.getItem
withhistory.state
- Replace
sessionStorage.removeItem
withhistory.replaceState
Another benefit of this is that the History API only retains data if you use its methods, in this case clicking the Back button, so there is no need to store the URL to check against the current location.
Because I’m better at writing code than writing blog posts and this is already the longest post I’ve written, here is the end result:
let savedOptions = history.state;
if ( savedOptions !== null ) {
savedOptions = JSON.parse( savedOptions );
initialCurrentPage = savedOptions.currentPage;
initialTaxFilter = savedOptions.taxFilter;
initialAlphaFilter = savedOptions.alphaFilter;
}
history.replaceState( null, null );
const writeSessionStorage = () => {
history.pushState(
savedState,
window.document.title,
window.location.href
);
}
<a
className="card-link"
rel="bookmark"
onClick={ () => {
props.storageCallback();
} }
href={ linkURL }
>
…and now we retain the state whenever a user clicks the back button but otherwise just load the default state.
Leave a Reply