dioxus_fullstack_hooks/
history.rs

1//! A history provider for fullstack apps that is compatible with hydration.
2
3use std::cell::OnceCell;
4
5use dioxus_core::{
6    prelude::{generation, queue_effect},
7    schedule_update,
8};
9use dioxus_history::History;
10
11// If we are currently in a scope and this is the first run then queue a rerender
12// for after hydration
13fn match_hydration<O>(
14    during_hydration: impl FnOnce() -> O,
15    after_hydration: impl FnOnce() -> O,
16) -> O {
17    if generation() == 0 {
18        let update = schedule_update();
19        queue_effect(move || update());
20        during_hydration()
21    } else {
22        after_hydration()
23    }
24}
25
26/// A history provider for fullstack apps that is compatible with hydration.
27#[derive(Clone)]
28pub struct FullstackHistory<H> {
29    initial_route: OnceCell<String>,
30    #[cfg(feature = "server")]
31    in_hydration_context: std::cell::Cell<bool>,
32    history: H,
33}
34
35impl<H> FullstackHistory<H> {
36    /// Create a new `FullstackHistory` with the given history.
37    pub fn new(history: H) -> Self {
38        Self {
39            initial_route: OnceCell::new(),
40            #[cfg(feature = "server")]
41            in_hydration_context: std::cell::Cell::new(false),
42            history,
43        }
44    }
45
46    /// Create a new `FullstackHistory` with the given history and initial route.
47    pub fn new_server(history: H) -> Self
48    where
49        H: History,
50    {
51        let initial_route = history.current_route();
52        let history = Self::new(history);
53        history.initial_route.set(initial_route).unwrap();
54        history
55    }
56
57    /// Get the initial route of the history.
58    fn initial_route(&self) -> String {
59        let entry = dioxus_fullstack_protocol::serialize_context().create_entry();
60        let route = self.initial_route.get_or_init(|| {
61            entry
62                .get()
63                .expect("Failed to get initial route from hydration context")
64        });
65        #[cfg(feature = "server")]
66        if !self.in_hydration_context.get() {
67            entry.insert(route, std::panic::Location::caller());
68            self.in_hydration_context.set(true);
69        }
70        route.clone()
71    }
72}
73
74impl<H: History> History for FullstackHistory<H> {
75    fn current_prefix(&self) -> Option<String> {
76        self.history.current_prefix()
77    }
78
79    fn can_go_back(&self) -> bool {
80        match_hydration(|| false, || self.history.can_go_back())
81    }
82
83    fn can_go_forward(&self) -> bool {
84        match_hydration(|| false, || self.history.can_go_forward())
85    }
86
87    fn external(&self, url: String) -> bool {
88        self.history.external(url)
89    }
90
91    fn updater(&self, callback: std::sync::Arc<dyn Fn() + Send + Sync>) {
92        self.history.updater(callback)
93    }
94
95    fn include_prevent_default(&self) -> bool {
96        self.history.include_prevent_default()
97    }
98
99    fn current_route(&self) -> String {
100        match_hydration(|| self.initial_route(), || self.history.current_route())
101    }
102
103    fn go_back(&self) {
104        self.history.go_back();
105    }
106
107    fn go_forward(&self) {
108        self.history.go_forward();
109    }
110
111    fn push(&self, route: String) {
112        self.history.push(route);
113    }
114
115    fn replace(&self, path: String) {
116        self.history.replace(path);
117    }
118}