Skip to main content

dioxus_history/
lib.rs

1use dioxus_core::{provide_context, provide_root_context};
2use std::{rc::Rc, sync::Arc};
3
4mod memory;
5pub use memory::*;
6
7/// Get the history provider for the current platform if the platform doesn't implement a history functionality.
8pub fn history() -> Rc<dyn History> {
9    match dioxus_core::try_consume_context::<Rc<dyn History>>() {
10        Some(history) => history,
11        None => {
12            tracing::error!(
13                "Unable to find a history provider in the renderer. Make sure your renderer supports the Router. Falling back to the in-memory history provider."
14            );
15            provide_root_context(Rc::new(MemoryHistory::default()))
16        }
17    }
18}
19
20/// Provide a history context to the current component.
21pub fn provide_history_context(history: Rc<dyn History>) {
22    provide_context(history);
23}
24
25pub trait History {
26    /// Get the path of the current URL.
27    ///
28    /// **Must start** with `/`. **Must _not_ contain** the prefix.
29    ///
30    /// ```rust
31    /// # use dioxus::prelude::*;
32    /// # #[component]
33    /// # fn Index() -> Element { VNode::empty() }
34    /// # #[component]
35    /// # fn OtherPage() -> Element { VNode::empty() }
36    /// #[derive(Clone, Routable, Debug, PartialEq)]
37    /// enum Route {
38    ///     #[route("/")]
39    ///     Index {},
40    ///     #[route("/some-other-page")]
41    ///     OtherPage {},
42    /// }
43    /// let mut history = dioxus::history::MemoryHistory::default();
44    /// assert_eq!(history.current_route(), "/");
45    ///
46    /// history.push(Route::OtherPage {}.to_string());
47    /// assert_eq!(history.current_route(), "/some-other-page");
48    /// ```
49    #[must_use]
50    fn current_route(&self) -> String;
51
52    /// Get the current path prefix of the URL.
53    ///
54    /// Not all [`History`]s need a prefix feature. It is meant for environments where a
55    /// dioxus-router-core-routed application is not running on `/`. The [`History`] is responsible
56    /// for removing the prefix from the dioxus-router-core-internal path, and also for adding it back in
57    /// during navigation. This functions value is only used for creating `href`s (e.g. for SSR or
58    /// display (but not navigation) in a web app).
59    fn current_prefix(&self) -> Option<String> {
60        None
61    }
62
63    /// Check whether there is a previous page to navigate back to.
64    ///
65    /// If a [`History`] cannot know this, it should return [`true`].
66    ///
67    /// ```rust
68    /// # use dioxus::prelude::*;
69    /// # #[component]
70    /// # fn Index() -> Element { VNode::empty() }
71    /// # fn Other() -> Element { VNode::empty() }
72    /// #[derive(Clone, Routable, Debug, PartialEq)]
73    /// enum Route {
74    ///     #[route("/")]
75    ///     Index {},
76    ///     #[route("/other")]
77    ///     Other {},
78    /// }
79    /// let mut history = dioxus::history::MemoryHistory::default();
80    /// assert_eq!(history.can_go_back(), false);
81    ///
82    /// history.push(Route::Other {}.to_string());
83    /// assert_eq!(history.can_go_back(), true);
84    /// ```
85    #[must_use]
86    fn can_go_back(&self) -> bool {
87        true
88    }
89
90    /// Go back to a previous page.
91    ///
92    /// If a [`History`] cannot go to a previous page, it should do nothing. This method
93    /// might be called, even if `can_go_back` returns [`false`].
94    ///
95    /// ```rust
96    /// # use dioxus::prelude::*;
97    /// # #[component]
98    /// # fn Index() -> Element { VNode::empty() }
99    /// # #[component]
100    /// # fn OtherPage() -> Element { VNode::empty() }
101    /// #[derive(Clone, Routable, Debug, PartialEq)]
102    /// enum Route {
103    ///     #[route("/")]
104    ///     Index {},
105    ///     #[route("/some-other-page")]
106    ///     OtherPage {},
107    /// }
108    /// let mut history = dioxus::history::MemoryHistory::default();
109    /// assert_eq!(history.current_route(), "/");
110    ///
111    /// history.go_back();
112    /// assert_eq!(history.current_route(), "/");
113    ///
114    /// history.push(Route::OtherPage {}.to_string());
115    /// assert_eq!(history.current_route(), "/some-other-page");
116    ///
117    /// history.go_back();
118    /// assert_eq!(history.current_route(), "/");
119    /// ```
120    fn go_back(&self);
121
122    /// Check whether there is a future page to navigate forward to.
123    ///
124    /// If a [`History`] cannot know this, it should return [`true`].
125    ///
126    /// ```rust
127    /// # use dioxus::prelude::*;
128    /// # #[component]
129    /// # fn Index() -> Element { VNode::empty() }
130    /// # #[component]
131    /// # fn OtherPage() -> Element { VNode::empty() }
132    /// #[derive(Clone, Routable, Debug, PartialEq)]
133    /// enum Route {
134    ///     #[route("/")]
135    ///     Index {},
136    ///     #[route("/some-other-page")]
137    ///     OtherPage {},
138    /// }
139    /// let mut history = dioxus::history::MemoryHistory::default();
140    /// assert_eq!(history.can_go_forward(), false);
141    ///
142    /// history.push(Route::OtherPage {}.to_string());
143    /// assert_eq!(history.can_go_forward(), false);
144    ///
145    /// history.go_back();
146    /// assert_eq!(history.can_go_forward(), true);
147    /// ```
148    #[must_use]
149    fn can_go_forward(&self) -> bool {
150        true
151    }
152
153    /// Go forward to a future page.
154    ///
155    /// If a [`History`] cannot go to a previous page, it should do nothing. This method
156    /// might be called, even if `can_go_forward` returns [`false`].
157    ///
158    /// ```rust
159    /// # use dioxus::prelude::*;
160    /// # #[component]
161    /// # fn Index() -> Element { VNode::empty() }
162    /// # #[component]
163    /// # fn OtherPage() -> Element { VNode::empty() }
164    /// #[derive(Clone, Routable, Debug, PartialEq)]
165    /// enum Route {
166    ///     #[route("/")]
167    ///     Index {},
168    ///     #[route("/some-other-page")]
169    ///     OtherPage {},
170    /// }
171    /// let mut history = dioxus::history::MemoryHistory::default();
172    /// history.push(Route::OtherPage {}.to_string());
173    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
174    ///
175    /// history.go_back();
176    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
177    ///
178    /// history.go_forward();
179    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
180    /// ```
181    fn go_forward(&self);
182
183    /// Go to another page.
184    ///
185    /// This should do three things:
186    /// 1. Merge the current URL with the `path` parameter (which may also include a query part).
187    /// 2. Remove the previous URL to the navigation history.
188    /// 3. Clear the navigation future.
189    ///
190    /// ```rust
191    /// # use dioxus::prelude::*;
192    /// # #[component]
193    /// # fn Index() -> Element { VNode::empty() }
194    /// # #[component]
195    /// # fn OtherPage() -> Element { VNode::empty() }
196    /// #[derive(Clone, Routable, Debug, PartialEq)]
197    /// enum Route {
198    ///     #[route("/")]
199    ///     Index {},
200    ///     #[route("/some-other-page")]
201    ///     OtherPage {},
202    /// }
203    /// let mut history = dioxus::history::MemoryHistory::default();
204    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
205    ///
206    /// history.push(Route::OtherPage {}.to_string());
207    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
208    /// assert!(history.can_go_back());
209    /// ```
210    fn push(&self, route: String);
211
212    /// Replace the current page with another one.
213    ///
214    /// This should merge the current URL with the `path` parameter (which may also include a query
215    /// part). In contrast to the `push` function, the navigation history and future should stay
216    /// untouched.
217    ///
218    /// ```rust
219    /// # use dioxus::prelude::*;
220    /// # #[component]
221    /// # fn Index() -> Element { VNode::empty() }
222    /// # #[component]
223    /// # fn OtherPage() -> Element { VNode::empty() }
224    /// #[derive(Clone, Routable, Debug, PartialEq)]
225    /// enum Route {
226    ///     #[route("/")]
227    ///     Index {},
228    ///     #[route("/some-other-page")]
229    ///     OtherPage {},
230    /// }
231    /// let mut history = dioxus::history::MemoryHistory::default();
232    /// assert_eq!(history.current_route(), Route::Index {}.to_string());
233    ///
234    /// history.replace(Route::OtherPage {}.to_string());
235    /// assert_eq!(history.current_route(), Route::OtherPage {}.to_string());
236    /// assert!(!history.can_go_back());
237    /// ```
238    fn replace(&self, path: String);
239
240    /// Navigate to an external URL.
241    ///
242    /// This should navigate to an external URL, which isn't controlled by the router. If a
243    /// [`History`] cannot do that, it should return [`false`], otherwise [`true`].
244    ///
245    /// Returning [`false`] will cause the router to handle the external navigation failure.
246    #[allow(unused_variables)]
247    fn external(&self, url: String) -> bool {
248        false
249    }
250
251    /// Provide the [`History`] with an update callback.
252    ///
253    /// Some [`History`]s may receive URL updates from outside the router. When such
254    /// updates are received, they should call `callback`, which will cause the router to update.
255    #[allow(unused_variables)]
256    fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>) {}
257
258    /// Whether the router should include the legacy prevent default attribute instead of the new
259    /// prevent default method. This should only be used by liveview.
260    fn include_prevent_default(&self) -> bool {
261        false
262    }
263}