diff --git a/packages/yew-router/src/navigator.rs b/packages/yew-router/src/navigator.rs index 48345609f83..dec228821b8 100644 --- a/packages/yew-router/src/navigator.rs +++ b/packages/yew-router/src/navigator.rs @@ -159,7 +159,7 @@ impl Navigator { pub(crate) fn prefix_basename<'a>(&self, route_s: &'a str) -> Cow<'a, str> { match self.basename() { Some(base) => { - if route_s.is_empty() && route_s.is_empty() { + if base.is_empty() && route_s.is_empty() { Cow::from("/") } else { Cow::from(format!("{base}{route_s}")) diff --git a/packages/yew-router/src/router.rs b/packages/yew-router/src/router.rs index b3becda9ea1..0c4b4dedf4c 100644 --- a/packages/yew-router/src/router.rs +++ b/packages/yew-router/src/router.rs @@ -1,6 +1,8 @@ //! Router Component. +use std::borrow::Cow; use std::rc::Rc; +use gloo::history::query::Raw; use yew::prelude::*; use yew::virtual_dom::AttrValue; @@ -72,16 +74,43 @@ fn base_router(props: &RouterProps) -> Html { basename, } = props.clone(); + let basename = basename.map(|m| strip_slash_suffix(&m).to_owned()); + let navigator = Navigator::new(history.clone(), basename.clone()); + + let old_basename = use_mut_ref(|| Option::::None); + let mut old_basename = old_basename.borrow_mut(); + if basename != *old_basename { + // If `old_basename` is `Some`, path is probably prefixed with `old_basename`. + // If `old_basename` is `None`, path may or may not be prefixed with the new `basename`, + // depending on whether this is the first render. + let old_navigator = Navigator::new( + history.clone(), + old_basename.as_ref().or(basename.as_ref()).cloned(), + ); + *old_basename = basename.clone(); + let location = history.location(); + let stripped = old_navigator.strip_basename(Cow::from(location.path())); + let prefixed = navigator.prefix_basename(&stripped); + + if prefixed != location.path() { + history + .replace_with_query(prefixed, Raw(location.query_str())) + .unwrap_or_else(|never| match never {}); + } else { + // Reaching here is possible if the page loads with the correct path, including the + // initial basename. In that case, the new basename would be stripped and then + // prefixed right back. While replacing the history would probably be harmless, + // we might as well avoid doing it. + } + } + + let navi_ctx = NavigatorContext { navigator }; + let loc_ctx = use_reducer(|| LocationContext { location: history.location(), ctr: 0, }); - let basename = basename.map(|m| strip_slash_suffix(&m).to_string()); - let navi_ctx = NavigatorContext { - navigator: Navigator::new(history.clone(), basename), - }; - { let loc_ctx_dispatcher = loc_ctx.dispatcher(); diff --git a/packages/yew-router/tests/link.rs b/packages/yew-router/tests/link.rs index 50510b79242..8e3f5bc5f41 100644 --- a/packages/yew-router/tests/link.rs +++ b/packages/yew-router/tests/link.rs @@ -1,5 +1,8 @@ +use std::sync::atomic::{AtomicU8, Ordering}; use std::time::Duration; +use gloo::utils::window; +use js_sys::{JsString, Object, Reflect}; use serde::{Deserialize, Serialize}; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use yew::functional::function_component; @@ -47,8 +50,20 @@ enum Routes { Search, } +#[derive(PartialEq, Properties)] +struct NavigationMenuProps { + #[prop_or(None)] + assertion: Option, +} + #[function_component(NavigationMenu)] -fn navigation_menu() -> Html { +fn navigation_menu(props: &NavigationMenuProps) -> Html { + let navigator = use_navigator().unwrap(); + let location = use_location().unwrap(); + if let Some(assertion) = props.assertion { + assertion(&navigator, &location); + } + html! {