History#replaceState trigger navigation event with React router
Description of the bug
When using React router, History#pushState and #replaceState trigger navigation event (trigger HISTORY, coming from a dom event "ui-navigate"). Even if only query parameters are changed.
With Flow Router, no navigation event occurs, not matter which part of the path has changed.
May relate to
- #19580 Also has something to do with breaking behavior change in History#replaceState with React Router
- #19540 Also has something to do with undesired navigation event
- #19494 Also has something to do with undesired HISTORY navigation event if the target URL does not exactly match the current one
- #19539 This sounds like a similar fix, but only applied to pushState. Indeed, pushState work as Flow Router and also as I would expect.
Expected behavior
No navigation events on History#pushState and #replaceState. If a navigation event is desired, I would use UI.navigate.
Minimal reproducible example
I modified the example from a comment in #19580
@Route(value = "test/:path?")
public class TestView extends VerticalLayout implements BeforeEnterObserver, BeforeLeaveObserver {
public TestView() {
add(new Button("replace state - no query params",
event -> UI.getCurrent().getPage().getHistory().replaceState(null, "test")));
// no navigation only if the current URL is the same including query params
add(new Button("replace state - push query param",
event -> UI.getCurrent().getPage().getHistory().replaceState(null, "test?new")));
// no navigation only if the current URL is the same including query params
add(new Button("replace state - change path param",
event -> UI.getCurrent().getPage().getHistory().replaceState(null, "test/new")));
// no navigation only if the current URL is the same including query params
add(new Button("push state - no query params",
event -> UI.getCurrent().getPage().getHistory().pushState(null, "test")));
// no navigation
add(new Button("push state - query param",
event -> UI.getCurrent().getPage().getHistory().pushState(null, "test?new")));
// no navigation
add(new Button("push state - change path param",
event -> UI.getCurrent().getPage().getHistory().pushState(null, "test/new")));
// no navigation
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
add(new Div("beforeEnter"));
}
@Override
public void beforeLeave(BeforeLeaveEvent beforeLeaveEvent) {
add(new Div("beforeLeave"));
}
}
Versions
- Vaadin / Flow version: 24.4.3
- Java version: 17
- OS version: Windows
- Browser version (if applicable):
- Application Server (if applicable): Spring Boot
- IDE (if applicable):
I can confirm that the following code:
ui.get().getPage().getHistory().replaceState(null, urlWithParameters);
cause the following issue: https://github.com/vaadin/flow/issues/19540
The issue was triaged and currently added to the backlog priority queue for further investigation
@mcollovati Thanks for looking into this. Is there any chance to get this fixed? I'm stuck on the prerelease version because of this issue and unable to upgrade to the stable version :(
We will start looking at this issue soon, but currently I cannot provide any estimate on when the bug will be fixed
This is blocking use from upgrading as well. Is there any update? Thanks
@alexanoid what is the latest prerelease version that works for you? Looks like we'll need to downgrade as well then for a while.
@alexanoid what is the latest prerelease version that works for you? Looks like we'll need to downgrade as well then for a while.
24.4.0.beta5
I see an issue with the no navigation functionality shown by the example also. So now the route has a path parameter, but as there is no navigation event for the push state in
add(new Button("push state - change path param",
event -> UI.getCurrent().getPage().getHistory().pushState(null, "test/new")));
// no navigation
we actually do not get the new path parameter to the server at all.
So the replaceState that sends the data to the server seems like the correct functionality as that gets us the parameter new where as for the pushState at the moment it doesn't and that doesn't seem correct.
for the query parameter change I would also expect to get it to the server, but the question is should all the events be fired (beforeEnter, beforeLeave, afterNavigation, setParameter) or should a navigation to the same target only execute beforeEnter and setParameter.
I feel we should get a comprehensive list of navigation expectations to get the full set working as expected instead of making exceptions here and there for the navigation.
As a workaround if for some reason the url update needs to happen "outside" of all handling you can just call
JsonValue state = null;
String pathWithQueryParameters = "test/new";
ui.getPage().executeJs("setTimeout(() => { window.history.replaceState($0, '', $1); })", state, pathWithQueryParameters);
But note that this may break forward/back navigation.
If I want to have all navigation events, I I would just use UI#navigate. Isn’t the whole point of the History class that it only pushes/replaces the state without firing events?
Well it is used by the framework to handle url updates for navigation etc. so one one hand yes, but on the other hand just directly running the push and replace functions breaks the react-router and unknowingly allowing that through the api is not that good either.
If only using Flow and no Hilla/react then falling back to vaadin-router for now is a possibility by setting the vaadin.react.enable property to false.
I can confirm that in my Flow application getUI().get().getPage().getHistory().replaceState(null, deepLinkingUrl);
works fine again after setting vaadin.react.enable property to false.
This ticket/PR has been released with Vaadin 24.4.9.
This ticket/PR has been released with Vaadin 24.5.0.