diff --git a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx index e3e571470c3..687a1598886 100644 --- a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx +++ b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx @@ -28,7 +28,7 @@ import { useBlocker, useLocation, useNavigate, - type NavigateOptions, + type NavigateOptions, useHref, } from "react-router-dom"; import type { AgnosticRouteObject } from '@remix-run/router'; import { createPortal } from "react-dom"; @@ -273,10 +273,12 @@ function Flow() { }); const location = useLocation(); const navigated = useRef(false); + const blockerHandled = useRef(false); const fromAnchor = useRef(false); const containerRef = useRef(undefined); const roundTrip = useRef | undefined>(undefined); const queuedNavigate = useQueuedNavigate(roundTrip, navigated); + const basename = useHref('/'); // portalsReducer function is used as state outside the Flow component. const [portals, dispatchPortalAction] = useReducer(portalsReducer, []); @@ -361,8 +363,18 @@ function Flow() { useEffect(() => { if (blocker.state === 'blocked') { + if(blockerHandled.current) { + // Blocker is handled and the new navigation + // gets queued to be executed after the current handling ends. + const {pathname, state} = blocker.location; + queuedNavigate(pathname.substring(basename.length), true, { state: state, replace: true }); + return; + } + blockerHandled.current = true; let blockingPromise: any; roundTrip.current = new Promise((resolve,reject) => blockingPromise = {resolve:resolve,reject:reject}); + // Release blocker handling after promise is fulfilled + roundTrip.current.then(() => blockerHandled.current = false, () => blockerHandled.current = false); // Proceed to the blocked location, unless the navigation originates from a click on a link. // In that case continue with function execution and perform a server round-trip diff --git a/flow-tests/test-react-router/pom-production.xml b/flow-tests/test-react-router/pom-production.xml index f0969878cd1..a9bc48f6154 100644 --- a/flow-tests/test-react-router/pom-production.xml +++ b/flow-tests/test-react-router/pom-production.xml @@ -34,6 +34,11 @@ ${project.version} + + com.vaadin + flow-react + ${project.version} + diff --git a/flow-tests/test-react-router/pom.xml b/flow-tests/test-react-router/pom.xml index 38ac1b8764e..4c1096eac73 100644 --- a/flow-tests/test-react-router/pom.xml +++ b/flow-tests/test-react-router/pom.xml @@ -38,6 +38,12 @@ ${project.version} + + com.vaadin + flow-react + ${project.version} + + diff --git a/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx b/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx new file mode 100644 index 00000000000..5fdef49d702 --- /dev/null +++ b/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx @@ -0,0 +1,22 @@ +import {useNavigate} from "react-router-dom"; +import { + ReactAdapterElement, + RenderHooks +} from "Frontend/generated/flow/ReactAdapter"; + + class NavigateView extends ReactAdapterElement { + protected render(hooks: RenderHooks): React.ReactElement | null { + const navigate = useNavigate(); + + return ( + <> +

This is a simple view for a React route

+ + + ); + } + } + +customElements.define('navigate-view', NavigateView); \ No newline at end of file diff --git a/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java b/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java new file mode 100644 index 00000000000..41cfa2bf0f7 --- /dev/null +++ b/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.flow; + +import com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.react.ReactAdapterComponent; +import com.vaadin.flow.router.Route; + +/** + * Test view for vaadin/flow#20404 Set network to slow 4G and quickly click on + * button. No console exceptions should be shown. + */ +@Route("com.vaadin.flow.ReactNavigateView") +@Tag("navigate-view") +@JsModule("NavigateView.tsx") +public class ReactNavigateView extends ReactAdapterComponent { + +}