Search code examples
javascriptfetch-apihtml5-history

Browser history broken when using push state


I have the following code in my application which tries to retrieve some data from a URL and then update the browser with the new URL.

const response = await fetch(url);

if (response.ok) {
    ...

    window.history.pushState({ loaded: true }, null, url);
}

Now every time I click to re-fetch the data (with a new URL) the URL in the browser is updated successfully. However if I click back it does nothing unless I click enough times for it to go back to the referred page.

My first fix was to change the code above to the following:

const response = await fetch(url);

if (response.ok) {
    ...

    if (window.history.state && window.history.state.loaded)
        window.history.pushState({ loaded: true }, null, url);
    else
        window.history.replaceState({ loaded: true }, null);
}

Now when I click back after it initially loads it goes back to the referred page as I simply replace the state and don't push anything to the history stack, before I would have had to click back twice. However I still have the issue that when I re-fetch the data it will now push to the history stack and nothing happens when I click back.

I thought the easiest thing would be to reload the page when I click back and the data has loaded. Therefore I tried the following:

window.addEventListener('popstate', e => {
    if (e.state && e.state.loaded)
        window.location.href = window.location.pathname + window.location.search;
});

However now I can't seem to go back to the referred page. This is because even though I reload the page (in the "popstate" event listener) when it fetches the data it says the state has loaded.

I thought this would be null after the reload but since it's not I thought I would force the browser to make sure the initial state is null when it first loads:

window.addEventListener('load', e => {
    window.history.replaceState(null, null);
});

However whilst this initially appeared to work I found this was only because I had the developer tools running (with caching disabled).

I'm sure I'm missing something basic as this seems to be quite a common problem. I'd appreciate it if someone could show me what I am doing wrong.


Solution

  • I've found that changing my fetch request to the following seems to work:

    await fetch(url, { cache: 'no-store' });
    

    Alternatively adding an addional query string parameter to the fetched url also works.