History: Page is refreshed by calling history.go(), while history.back() isn't

Created on 4 Aug 2020  路  6Comments  路  Source: ReactTraining/history

We have iframe based micro-frontends. There is a need of changing the state of the inside app from outside one without full-reloading of the inside one.
There is <BrowserRouter /> which uses Native History API.

I have tested the behavior on single app without any iframes.

When I click anchors in the app, React router doesn't refresh the whole app, and replaces parts of the app. In my case -- it's pagination.

I can call window.history.back() and it will not trigger page refresh.
The same, I can do window.history.go(-1) and it will not trigger page referesh.

Then I decided what if I make window.history.pushState(null, null, 'page/2') and call window.history.go() -- page will be refreshed from scratch.

I can solve my task by replacing BrowserRouter by Router and exposing history object to window.ReactRouterHistory, but I think it's too much, when I can do window.history.back() without a page refresh.

Is it a bug? Why the page _is fully refreshed_ by go(), but _isn't_ when go(-1)?
Is there a proper way to do this?

Thank you!

All 6 comments

Can you clarify what exactly you're doing?

If you invoke window.history.go() without any parameters, that will cause a page refresh, same as window.history.go(0). That behavior is part of specifications, as described here: https://developer.mozilla.org/en-US/docs/Web/API/History/go

window.history.back() is essentially just an alias for window.history.go(-1). In the history library, it's .goBack and also just an alias.

I'm not sure why you call go without a parameter. If you want to go back, then you have to call go(-1). And also: Why are you bypassing the history library in the first place? react-router provides you easy access to the history object it uses.

@StringEpsilon Thank you for the answer!

My goal is to trigger a new route in the child iframe react app without page refresh.
When I click on links inside this iframe, React router works as expected: page isn't refreshed, RR just replaces parts of react tree.

But when I try smth like iframeTag.contentWindow.location.href = 'new location' the iframe page is going to full refresh.
I thought this is how iframe works itself. To achieve my goal I decided to try smth else: an access of history object.

I did some clicks in the iframe app (RR did its thing). And then I did iframeTag.contentWindow.history.back() -- it worked! No full-page refresh, React Router did its thing: it changed only part of react tree.

Cool! I thought. I can trigger the iframe history from the parent window.

But my initial goal was not about back and forward. It was about setting a specific route for the child app.

So, how I can do that?

Imagine, I'm on the following url page/1. I tried to use iframe.contentWindow.history.pushState(null, null, 'page/2') but nothing happens: I'm still on page/1. (There is an issue that RR can't listen to pushState changes). So, I had to call iframe.contentWindow.history.go() (or go(0) to trigger it). Then page makes a full refresh to a page/2. So, basically the same stuff as location.href = 'page/2'.

I also tried this stuff without any iframes, to simplify the task: window.history.back() doesnt' trigger a full refresh, while window.history.go() or window.history.go(0) does.

So, there is my question, how to do this? How to change a route in an iframed app?
Thank you!

History version 5 has support for custom window objects to enable easier use of iframes (see #624 ). I'm afraid you just have to wait for react-router version 6 to come out or risk the beta.

Also, I think you should probably seek advice on stackoverflow and or reactiflux on this. https://www.reactiflux.com/ This is clearly a support question and not a feature request or bug report.

@StringEpsilon Thank you for sharing the 624, but it's not the case. The 624 PR is about desyncronization of history objects and about the ability to set such behavior.

Well, I believe it is a bug or at least a feature request. I was asking for an advise only after discovering an issue that works not as expected.

I will try to explain why I think this is a bug/feature request.

The whole thing is not about iframes. It's just when such behavior is crucial instead of optional.

  1. React Router in BrowserRouter component uses a history library, which is connected to native History API
  2. I expect I can access the history object via window.history and do some manipulation with it.
  3. I expect I can use window.history.back() and window.history.forward() to trigger React Router. And it works. RR replaces parts of the tree
  4. The same way I expect that I can set a specific route via window.history. There is a pushState method which can set url.
  5. I expect I can set new URL by calling window.history.pushState(null, null, 'the new URL') that should be accepted by React Router to trigger the route. But nothing happens.

Unfortunately, while delta in go([delta]) equals to 0, according History API is the same as location.reload() which causes full refresh. Therefore it could not be overrided by React Router.

From the other hand, if React Router works correctly when I call history.back() or history.go(<N>), I expect to set a specific route that will not cause the full refresh.

I think, that this is a bug or missing functionality.

Unfortunatly, there is no API to get notified about window.history.pushState() and window.history.replaceState(), as the browser will only fire onpopstate when the user navigates.

This library would need to monkeypatch the two functions to add in events or a callback.

It sounds like you want this library to be something it's not. This library isn't meant to replace the browser's built-in history API. It's just a thin layer the router uses to communicate with the browser. But users are still free to call the browser's built-in methods however they wish.

Was this page helpful?
0 / 5 - 0 ratings