This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_router/
router.rs

1//! Router Component.
2use std::borrow::Cow;
3use std::rc::Rc;
4
5use gloo::history::query::Raw;
6use yew::prelude::*;
7use yew::virtual_dom::AttrValue;
8
9use crate::history::{AnyHistory, BrowserHistory, HashHistory, History, Location};
10use crate::navigator::Navigator;
11use crate::utils::{base_url, strip_slash_suffix};
12
13/// Props for [`Router`].
14#[derive(Properties, PartialEq, Clone)]
15pub struct RouterProps {
16    #[prop_or_default]
17    pub children: Html,
18    pub history: AnyHistory,
19    #[prop_or_default]
20    pub basename: Option<AttrValue>,
21}
22
23#[derive(Clone)]
24pub(crate) struct LocationContext {
25    location: Location,
26    // Counter to force update.
27    ctr: u32,
28}
29
30impl LocationContext {
31    pub fn location(&self) -> Location {
32        self.location.clone()
33    }
34}
35
36impl PartialEq for LocationContext {
37    fn eq(&self, rhs: &Self) -> bool {
38        self.ctr == rhs.ctr
39    }
40}
41
42impl Reducible for LocationContext {
43    type Action = Location;
44
45    fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
46        Self {
47            location: action,
48            ctr: self.ctr + 1,
49        }
50        .into()
51    }
52}
53
54#[derive(Clone, PartialEq)]
55pub(crate) struct NavigatorContext {
56    navigator: Navigator,
57}
58
59impl NavigatorContext {
60    pub fn navigator(&self) -> Navigator {
61        self.navigator.clone()
62    }
63}
64
65/// The base router.
66///
67/// The implementation is separated to make sure <Router /> has the same virtual dom layout as
68/// the <BrowserRouter /> and <HashRouter />.
69#[function_component(BaseRouter)]
70fn base_router(props: &RouterProps) -> Html {
71    let RouterProps {
72        history,
73        children,
74        basename,
75    } = props.clone();
76
77    let basename = basename.map(|m| strip_slash_suffix(&m).to_owned());
78    let navigator = Navigator::new(history.clone(), basename.clone());
79
80    let old_basename = use_mut_ref(|| Option::<String>::None);
81    let mut old_basename = old_basename.borrow_mut();
82    if basename != *old_basename {
83        // If `old_basename` is `Some`, path is probably prefixed with `old_basename`.
84        // If `old_basename` is `None`, path may or may not be prefixed with the new `basename`,
85        // depending on whether this is the first render.
86        let old_navigator = Navigator::new(
87            history.clone(),
88            old_basename.as_ref().or(basename.as_ref()).cloned(),
89        );
90        *old_basename = basename.clone();
91        let location = history.location();
92        let stripped = old_navigator.strip_basename(Cow::from(location.path()));
93        let prefixed = navigator.prefix_basename(&stripped);
94
95        if prefixed != location.path() {
96            history
97                .replace_with_query(prefixed, Raw(location.query_str()))
98                .unwrap_or_else(|never| match never {});
99        } else {
100            // Reaching here is possible if the page loads with the correct path, including the
101            // initial basename. In that case, the new basename would be stripped and then
102            // prefixed right back. While replacing the history would probably be harmless,
103            // we might as well avoid doing it.
104        }
105    }
106
107    let navi_ctx = NavigatorContext { navigator };
108
109    let loc_ctx = use_reducer(|| LocationContext {
110        location: history.location(),
111        ctr: 0,
112    });
113
114    {
115        let loc_ctx_dispatcher = loc_ctx.dispatcher();
116
117        use_effect_with(history, move |history| {
118            let history = history.clone();
119            // Force location update when history changes.
120            loc_ctx_dispatcher.dispatch(history.location());
121
122            let history_cb = {
123                let history = history.clone();
124                move || loc_ctx_dispatcher.dispatch(history.location())
125            };
126
127            let listener = history.listen(history_cb);
128
129            // We hold the listener in the destructor.
130            move || {
131                std::mem::drop(listener);
132            }
133        });
134    }
135
136    html! {
137        <ContextProvider<NavigatorContext> context={navi_ctx}>
138            <ContextProvider<LocationContext> context={(*loc_ctx).clone()}>
139                {children}
140            </ContextProvider<LocationContext>>
141        </ContextProvider<NavigatorContext>>
142    }
143}
144
145/// The Router component.
146///
147/// This provides location and navigator context to its children and switches.
148///
149/// If you are building a web application, you may want to consider using [`BrowserRouter`] instead.
150///
151/// You only need one `<Router />` for each application.
152#[function_component(Router)]
153pub fn router(props: &RouterProps) -> Html {
154    html! {
155        <BaseRouter ..{props.clone()} />
156    }
157}
158
159/// Props for [`BrowserRouter`] and [`HashRouter`].
160#[derive(Properties, PartialEq, Clone)]
161pub struct ConcreteRouterProps {
162    pub children: Html,
163    #[prop_or_default]
164    pub basename: Option<AttrValue>,
165}
166
167/// A [`Router`] that provides location information and navigator via [`BrowserHistory`].
168///
169/// This Router uses browser's native history to manipulate session history
170/// and uses regular URL as route.
171///
172/// # Note
173///
174/// The router will by default use the value declared in `<base href="..." />` as its basename.
175/// You may also specify a different basename with props.
176#[function_component(BrowserRouter)]
177pub fn browser_router(props: &ConcreteRouterProps) -> Html {
178    let ConcreteRouterProps { children, basename } = props.clone();
179    let history = use_state(|| AnyHistory::from(BrowserHistory::new()));
180
181    // We acknowledge based in `<base href="..." />`
182    let basename = basename.map(|m| m.to_string()).or_else(base_url);
183
184    html! {
185        <BaseRouter history={(*history).clone()} {basename}>
186            {children}
187        </BaseRouter>
188    }
189}
190
191/// A [`Router`] that provides location information and navigator via [`HashHistory`].
192///
193/// This Router uses browser's native history to manipulate session history
194/// and stores route in hash fragment.
195///
196/// # Warning
197///
198/// Prefer [`BrowserRouter`] whenever possible and use this as a last resort.
199#[function_component(HashRouter)]
200pub fn hash_router(props: &ConcreteRouterProps) -> Html {
201    let ConcreteRouterProps { children, basename } = props.clone();
202    let history = use_state(|| AnyHistory::from(HashHistory::new()));
203
204    html! {
205        <BaseRouter history={(*history).clone()} {basename}>
206            {children}
207        </BaseRouter>
208    }
209}