How to listen for changes to window.location.search in 2019
Asked Answered
T

1

12

I would like to update window.location.search without reloading the document and then execute a function based on the updated values in window.location.search.

When the user clicks a button, that button's onclick event fires the following function:

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');

So far so good. The window.location.search updates in the browser URL bar.

But I can't find a general background event listener which detects this update - something like onhashchange but for query strings.

The best I can come up with is:

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
window.history.back();

window.addEventListener('popstate', () => console.log(event.state.action));

This works but I'm convinced there must be a more elegant approach.

Threefold answered 21/9, 2019 at 11:7 Comment(0)
T
6

Final Answer:

I was mostly happy with the Second Attempt answer immediately below.

But I really didn't like:

history.back();
history.forward();

which I adopted to trigger the popstate event, manually.

In practice, I found this hack worked sometimes, it was slow at other times and on occasion it failed completely.

On paper, it feels too makeshift.

After repeatedly searching and experimenting (this was remarkably hard to find), I have finally found the correct syntax for manually firing a popstate event.

It's as simple as dispatching this:

new Event('popstate')

Like this:

let myEvent = new Event('popstate');
window.dispatchEvent(myEvent);

So the final code is:

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
let queryStringChange = new Event('popstate');
window.dispatchEvent(queryStringChange);

Best Answer I can come up with (Second Attempt):

Building on the following:

I know for certain that the query-string will only need to be checked either:

  1. when the page loads or reloads;

  2. when window.history.pushstate is invoked;

  3. or when (as @Kaiido astutely pointed out in the comments below) a page is accessed via the forward and back buttons

I also know that:

  • The window.load event listener covers Point 1.;
  • The window.popstate event listener covers Point 3.;

This only leaves Point 2.


Dealing with Point 2

As MDN reports:

Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event will be triggered by doing a browser action such as a click on the back or forward button (or calling history.back() or history.forward() in JavaScript).

Source: https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event

But this means that rather than using (as in my first attempt, below):

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');

checkQueryString(); // DIRECT INVOCATION OF FUNCTION

I can use instead:

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');
history.back();
history.forward();

This means that rather than needing to invoke the function directly, I can now allow the same window.popstate event listener covering Point 3 to do its work, giving me cleaner, more logically separated code.


N.B. I find it... weird... that pressing the forward button alone will fire the window.popstate event listener, but invoking window.history.pushState() will not... (necessitating the immediately subsequent addition of history.back(); history.forward(); to duplicate the functionality of a forward button).

But, as above, this is the best, cleanest, most optimised (in terms of logical separation and future code maintenance) solution I can come up with. If anyone has a dramatically better idea, I'll be happy to transfer the green tick.


First Attempt at an Answer:

I am tentatively concluding that there is no way to achieve this effect using a background event listener which can detect when the query string updates.

Instead, since I know for certain that the query-string will only need to be checked either:

  • when the page loads or reloads
  • when window.history.pushstate is invoked

I can use the following:

window.addEventListener('load', checkQueryString);

and

window.history.pushState({action : 'myAction'}, document.title, '?action=myAction');

checkQueryString();
Threefold answered 22/9, 2019 at 10:54 Comment(4)
You would also need to listen for popstate event in order to catch when your user will navigate through the browser's back-next buttons.Lamellate
Yes, excellent point. Thanks for catching that. So two Event Listeners then - one for load and one for popstate. If you want to post that as an alternative answer, I'm happy to mark your improved solution as the accepted answer.Threefold
Is there any diffrence between using window.dispatch(myEvent) and usinghistory.back()? Will using window.dispatch(myEvent) change the history object?Avunculate
Yes, there is a difference. history.back() (and history.forward()) will manipulate both the history object and change the url in the address bar. For me these were undesired side-effects. All I wanted to happen was for the onpopstate event to fire on window after I used the pushstate method on window.history.Threefold

© 2022 - 2024 — McMap. All rights reserved.