From 8d29c9fb7dc39a8506c6ec2c9717d4f5d2e734c5 Mon Sep 17 00:00:00 2001 From: Marco Collovati Date: Fri, 11 Sep 2026 06:15:07 +0000 Subject: [PATCH] docs: document that navigating to the shown view does nothing (#25629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #25580 **DX/docs** · flow-server · everyone who navigates to the view that is already shown Navigating to the view that is already shown does nothing, and the javadoc promised the opposite: it said the return value tells you whether the navigation happened, when it never does. Anyone who wanted the view rebuilt had no way to find the method that rebuilds it. Both are documented now; behaviour is unchanged. **Risks:** - ✅ Javadoc only, plus one test that pins the documented behaviour: no behaviour change, no API change, nothing deprecated. **Context.** The skip prevents navigation loops, because a `BeforeEnter` reroute and the client history sync go through the same call, so the behaviour stays as it is and only the javadoc was wrong. `refreshCurrentRoute(boolean)` already does the rebuild that was asked for. - Documented on all nine `UI.navigate` overloads that navigating to the location that is already shown does nothing: the view is not re-instantiated, no navigation lifecycle event fires, and `refreshCurrentRoute(boolean)` is the way to rebuild it. - Corrected `@return` on the seven overloads that return an `Optional`. It said "the view instance, if navigation actually happened"; the `Optional` holds the view that was already shown when the navigation was skipped, and is empty when a reroute or a forward went elsewhere. - Added the reciprocal cross-reference on `refreshCurrentRoute(boolean)`, which now says it is the way to rebuild the view that is currently shown and links back to `navigate`. - Added `UITest` coverage: navigating twice to the same target returns the same instance, and `refreshCurrentRoute(false)` replaces it. --- .../java/com/vaadin/flow/component/UI.java | 101 ++++++++++++++++-- .../com/vaadin/flow/component/UITest.java | 24 +++++ 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/component/UI.java b/flow-server/src/main/java/com/vaadin/flow/component/UI.java index 0830da716da..b7194aad023 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/UI.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/UI.java @@ -1053,6 +1053,11 @@ public Page getPage() { * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1064,9 +1069,14 @@ public Page getPage() { * @throws NotFoundException * in case there is no route defined for the given * navigationTarget. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @see #navigate(Class, Object) * @see #navigate(Class, RouteParameters) + * @see #refreshCurrentRoute(boolean) */ public Optional navigate( Class navigationTarget) { @@ -1100,6 +1110,11 @@ private Optional findCurrentNavigationTarget( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1109,7 +1124,11 @@ private Optional findCurrentNavigationTarget( * url parameter type * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1117,6 +1136,7 @@ private Optional findCurrentNavigationTarget( * @throws NotFoundException * in case there is no route defined for the given * navigationTarget matching the parameters. + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public > Optional navigate( @@ -1140,6 +1160,11 @@ public > Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1147,7 +1172,11 @@ public > Optional navigate( * navigation target to navigate to. * @param parameters * parameters to pass to view. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if navigationTarget is a {@link HasUrlParameter} with a * mandatory parameter, but parameters argument doesn't provide @@ -1156,6 +1185,7 @@ public > Optional navigate( * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 4.0 + * @see #refreshCurrentRoute(boolean) */ public Optional navigate(Class navigationTarget, RouteParameters parameters) { @@ -1179,6 +1209,11 @@ public Optional navigate(Class navigationTarget, * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1186,7 +1221,11 @@ public Optional navigate(Class navigationTarget, * navigation target to navigate to. * @param parameters * parameters to pass to view. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if navigationTarget is a {@link HasUrlParameter} with a * mandatory parameter, but parameters argument doesn't provide @@ -1195,6 +1234,7 @@ public Optional navigate(Class navigationTarget, * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 24.1.1 + * @see #refreshCurrentRoute(boolean) */ public Optional navigate(Class navigationTarget, RouteParam... parameters) { @@ -1216,6 +1256,11 @@ public Optional navigate(Class navigationTarget, * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1227,7 +1272,11 @@ public Optional navigate(Class navigationTarget, * url parameter type * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1236,6 +1285,7 @@ public Optional navigate(Class navigationTarget, * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 23.2 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public > Optional navigate( @@ -1268,6 +1318,11 @@ public > Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1277,7 +1332,11 @@ public > Optional navigate( * additional query parameters to pass to view * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1286,6 +1345,7 @@ public > Optional navigate( * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 24.1 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public Optional navigate( @@ -1310,6 +1370,11 @@ public Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1317,11 +1382,16 @@ public Optional navigate( * additional query parameters to pass to view * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws NotFoundException * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 23.2 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public Optional navigate( @@ -1351,9 +1421,15 @@ public Optional navigate( *

* Besides the navigation to the {@code location} this method also updates * the browser location (and page history). + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @see #navigate(String, QueryParameters) * @see Router#navigate(UI, Location, NavigationTrigger) + * @see #refreshCurrentRoute(boolean) * * @param location * the location to navigate to, not {@code null} @@ -1371,9 +1447,15 @@ public void navigate(String location) { *

* Besides the navigation to the {@code location} this method also updates * the browser location (and page history). + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @see #navigate(String) * @see Router#navigate(UI, Location, NavigationTrigger) + * @see #refreshCurrentRoute(boolean) * * @param locationString * the location to navigate to, not {@code null} @@ -1454,6 +1536,10 @@ public void navigate(String locationString, * Re-navigates to the current route. Also re-instantiates the route target * component, and optionally all layouts in the route chain. *

+ * This is the way to rebuild the view that is currently shown, since + * {@link #navigate(Class) navigating} to the location that is already shown + * does nothing. + *

* In development mode, local signal field values are automatically * transferred from the old component instance to the new one, preserving UI * state across refreshes. @@ -1462,6 +1548,7 @@ public void navigate(String locationString, * {@code true} to refresh all layouts in the route chain, * {@code false} to only refresh the route instance * @since 24.4 + * @see #navigate(Class) */ public void refreshCurrentRoute(boolean refreshRouteChain) { getInternals().refreshCurrentRoute(refreshRouteChain); diff --git a/flow-server/src/test/java/com/vaadin/flow/component/UITest.java b/flow-server/src/test/java/com/vaadin/flow/component/UITest.java index 51675661b7a..0c034b94e9a 100644 --- a/flow-server/src/test/java/com/vaadin/flow/component/UITest.java +++ b/flow-server/src/test/java/com/vaadin/flow/component/UITest.java @@ -94,6 +94,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -444,6 +445,29 @@ public void locationAfterServerNavigation() CoreMatchers.instanceOf(FooBarNavigationTarget.class)); } + @Test + public void navigateToShownView_notReinstantiated_shownInstanceReturned() + throws InvalidRouteConfigurationException { + UI ui = new UI(); + initUI(ui, "", null); + + FooBarNavigationTarget shownView = ui + .navigate(FooBarNavigationTarget.class).orElseThrow(); + + // Navigating to the location that is already shown does nothing, so + // the returned Optional holds the view that is already shown + FooBarNavigationTarget sameView = ui + .navigate(FooBarNavigationTarget.class).orElseThrow(); + + assertSame(shownView, sameView, + "Navigating to the shown view should not re-instantiate it"); + + ui.refreshCurrentRoute(false); + + assertNotSame(shownView, ui.getCurrentView(), + "refreshCurrentRoute should re-instantiate the shown view"); + } + @Test @Disabled("Check what is the new Router.navigate for JavaScriptUI") public void navigateWithParameters_delegateToRouter() {