43

I've read several question/answers on Stack Overflow and Googled the issue, I can't seem to get the event.state to come back with anything but null.

I got that it wont work with the jQuery event, but when I do something like this:

window.onpopstate = function (e) {
     console.log(e.state)
}

With something along the lines of a jQuery.ajax success calling

history.pushState({ lastUrl: data.State }, data.Title, data.UrlKey);

Neither Chrome (v19) or Firefox (v12) return anything but null? Am I missing something? Is it just not fully implemented?

Brian Tompsett - 汤莱恩
  • 5,195
  • 62
  • 50
  • 120
mihok
  • 579
  • 1
  • 4
  • 10
  • possible duplicate of [HTML history state issues on OS X Chrome 10.0.648.151](http://stackoverflow.com/questions/5418540/html-history-state-issues-on-os-x-chrome-10-0-648-151) – Derek 朕會功夫 Jun 19 '12 at 00:18
  • 4
    @Derek the question you provided is talking about popstate firing upon pageload. To clarify, at any time popstate fires, none of the data sent from a pushState function (ie. lastUrl in the above example) is available in the event object, this is the issue I'm inquiring about – mihok Jul 05 '12 at 14:55

5 Answers5

62

e.state refers to the second last state that was pushed. You need to have pushed state at least twice for e.state to not be null. This is because you should save the state when your site is loaded the first time and thereafter every time it changes state.

Jaco Briers
  • 1,633
  • 1
  • 20
  • 33
  • 1
    Solved my problem I asked here http://stackoverflow.com/questions/13107147/javascript-history-state-incorrect ... If you can write an answer for my question here, I can accept it. – Rohit Banga Oct 28 '12 at 19:34
  • 4
    General comment: I would recommend instead of overloading the function `window.onpopstate = function (e) {` simply "listen" to it `window.addEventListener("popstate", function(event) {`... because otherwise you'll cancel other implementation is has which you didn't re-write. – Ricky Levi Jan 10 '18 at 10:08
  • 1
    See my description of my problem with this same issue below. I did call the pushState many times every time the user (me) clicked a link. But the state was still always null. It only became non-null when I called pushState () "at least twice" IN THE ONLOAD-handler. I don't understand why calling it many times from the onclick -handler did not make it work. Did it perhaps have something to do with the 3rd argument to pushState() having a wrong value? Who knows. Perhaps. But calling the pushState() twice in the onload -handler solved the problem. – Panu Logic May 26 '18 at 02:32
  • 1
    Just to add to the answer above: instead of calling `pushState` twice, which adds 2 entries to history, I prefer to call `replaceState` w my desired state upon load. Then subsequent `pushState` calls work as expected. – Peleg Dec 04 '18 at 17:05
16

I think that this question needs a more clear explanation because the sentence "second last state that was pushed" in the other answers may lead to confusion.

When we do history.pushState we are pushing into the history stack a new State, that becomes the current one (as we can see from the navigation bar url.)

The window.onpopstate event means that the top history state is being popped out (taken away from the stack) , so the e.state will now point to the new new top state in the stack (as the navigation bar will point to the previous url).

MQ87
  • 938
  • 12
  • 29
3

I struggled with this same issue for many hours. Too many hours. The state-argument of onpopstate -handler was null even though I had called pushState() many times before I clicked the back-button.

I also observed that my onclick-handler which called pushState() caused the onpopstate -handler to be triggered. I believe onpopstate -handler should only get called due to user clicking on the back-button from what I read on the web. But that seemed not to be the case in my case.

Could there be a bug in the browser? Seems unlikely because I had the same or similar problem on both Chrome and FireFox. But possible. Or maybe there is a "bug in the spec" being too complicated to implement correctly. No public unit-tests showing how this should work.

Finally I arrived at a solution after which EVERYTHING started working. It was to put these two calls into my onload-handler:

pushState (myInitialState, null, href);
pushState (myInitialState, null, href);

So I have to make the same push-state() call TWICE in the onload-handler! After that my onpopstate -handler started getting arguments whose state was NOT null but a state I had previously passed as argument to pushState().

I don't really understand why it now works and why it didn't earlier when I called pushState ONLY ONCE.

I would like to understand why it works now but I already spent too much time with this getting it to work. If anybody has a reference to good example-code online which explains it that would be great.

Panu Logic
  • 1,495
  • 1
  • 13
  • 20
  • In my case popState would receive null state often (not always). Ensuring that there was an additional initial pushState call seems to have resolved the issue. – Ujjwal Singh Oct 01 '18 at 16:57
1

It will be helpful if you have ever learnt about the undo/redo stack, as the picture shows below.

enter image description here

In fact, the history is kept in a stack like this. Your current state is from current stack item, and let's assume there is a pointer to it; when you call history.back(), the pointer go to older item of the stack, the onpopstate event's state is the same as history.state by the way. You will just get the item which pointer currently pointing at, which modified by history.replaceState or added by history.pushState.

This process also explains why history.length doesn't change, because it represents the whole stack's length. When you call history.go(1), the pointer move back to the newer item.

When the pointer is at a middle position of stack, calling history.pushState will lead to all the items above the pointer popped, and new one added, also you can see history'length changes.

W.Leto
  • 571
  • 6
  • 9
0

As Jaco Briers mentioned, for popstate to work the way we think it should work, we need to first store the initial state to return to when you click on the browser's Back button. Here's a sample implementation using jQuery:

Sample Code

$(window).on({
  // Store initial state
  'load': function() {
    window.history.pushState({
      'html': '<div id="ajax-content">' + $('#ajax-content').html() + '</div>'
    }, '', document.URL);
  },
  // Handle browser Back/Forward button
  'popstate': function(e) {
    var oState = e.originalEvent.state;
    if (oState) {
      $('#ajax-content').replaceWith(oState.html);
    }
  }
});

...

// Update AJAX content
var sUrl = 'https://www.example.com/category?id=123&format=ajax';
$.ajax({
  type: 'GET',
  url: sUrl,
  success: function(sHtml) {
    $('#ajax-content').replaceWith(sHtml);
    window.history.pushState({
      'html': sHtml
    }, '', sUrl);
  }
});
thdoan
  • 15,024
  • 1
  • 46
  • 40