Explain Codes LogoExplain Codes Logo

How do I retrieve if the popstate event comes from back or forward actions with the HTML5 pushstate?

javascript
prompt-engineering
history-api
navigation-direction
Anton ShumikhinbyAnton Shumikhin·Sep 23, 2024
TLDR
history.state?.source === 'back' || history.state?.source === 'forward'

Check the history.state object for a source property to discern whether the popstate event signifies a back or forward action. Remember to assign 'back' or 'forward' as the source property when utilizing history.pushState.

Example:

// When pushing state, don't forget direction info // After all, we don't want our user to get lost now, do we? function pushStateWithSource(state, title, url, source) { history.pushState({ ...state, source }, title, url); } // Add your direction 'back' or 'forward' // It's like giving your user a compass pushStateWithSource({}, '', 'nextpage.html', 'forward'); // Listen for popstate event window.addEventListener('popstate', function(event) { // Aha! The user is moving! if(event.state?.source === 'back' || event.state?.source === 'forward') { console.log(`Navigated ${event.state.source}`); // Logs 'Navigated back' or 'Navigated forward' } });

As can be seen, we manually track the navigation direction, which is just us taking the situation into our own reliable hands!

Supplementing with UID tracker

To further enrich our direction tracking capabilities, we can introduce a unique incrementing ID (UID). This notable addition into the state object maintains a historical record of navigation alterations, which, in turn, gifts us the ability to accurately deduce the navigation direction.

Add unique incrementing IDs to state

Each push state harbors a UID that ascends in value:

let currentUid = -1; // Yep, we are starting at negative. Because...reasons. // Don't push without your ID! function pushStateWithUid(state, title, url) { const uid = ++currentUid; // Every push state gets a shiny new uid! history.pushState({ ...state, uid }, title, url); }

Using 'onpopstate' to discern direction

By appropriating the onpopstate event handler, we can ascertain direction:

window.addEventListener('popstate', function(event) { const direction = event.state.uid > currentUid ? 'forward' : 'back'; // Simplicity at its best, isn't it? currentUid = event.state.uid; // Staying up-to-date! console.log(`Navigated ${direction}`); // Prints either 'Navigated forward' or 'Navigated back' });

It's important to note, while most history entries will cooperate with our neat tracking system, those originating from external pages may play a less amiable game.

Sharp moves with Math.sign

Use powerhouse Math.sign for sharper direction deduction:

Pinpoint position using Math.sign

Check out this upgrade. We are dealing with position values now:

let currentPosition = 0; // You've got to start somewhere, right? // Still at it, pushing state function pushStateWithPosition(state, title, url) { history.pushState({ ...state, position: ++currentPosition }, title, url); } window.addEventListener('popstate', function(event) { // One function call to point the direction. Yes, please. const direction = Math.sign(event.state.position - currentPosition); currentPosition = event.state.position; // Updates the position const directionText = direction === 1 ? 'forward' : (direction === -1 ? 'back' : 'unknown'); console.log(`Navigated ${directionText}`); // Behold the power of Math.sign in action! });

Keep in mind, history entries from external pages may require a unique handle, and deep historical navigation can have its fun intricacies!

Marking history for clarity

Use timestamps or unique identifiers to mark each history entry, making the navigation crystal clear:

Example of history stamping

Add a timestamp to state for precision:

function pushStateWithTimeStamp(state, title, url) { const timeStamp = Date.now(); history.pushState({ ...state, timeStamp }, title, url); }

Handling stamped data

In each popstate event, compare timestamps to make the direction of navigation as clear as a sunny day.