Przeglądaj źródła

Merge pull request #168 from DioxusLabs/jk/use_route_subscribes

fix: use_route should subscribe to changes to the route
Jonathan Kelley 3 lat temu
rodzic
commit
0d8e9f5ed4

+ 30 - 7
packages/router/src/hooks/use_route.rs

@@ -1,4 +1,4 @@
-use dioxus_core::ScopeState;
+use dioxus_core::{ScopeId, ScopeState};
 use gloo::history::{HistoryResult, Location};
 use serde::de::DeserializeOwned;
 use std::{rc::Rc, str::FromStr};
@@ -8,6 +8,7 @@ use crate::RouterService;
 /// This struct provides is a wrapper around the internal router
 /// implementation, with methods for getting information about the current
 /// route.
+#[derive(Clone)]
 pub struct UseRoute {
     router: Rc<RouterService>,
 }
@@ -74,10 +75,32 @@ impl UseRoute {
 /// This hook provides access to information about the current location in the
 /// context of a [`Router`]. If this function is called outside of a `Router`
 /// component it will panic.
-pub fn use_route(cx: &ScopeState) -> UseRoute {
-    let router = cx
-        .consume_context::<RouterService>()
-        .expect("Cannot call use_route outside the scope of a Router component")
-        .clone();
-    UseRoute { router }
+pub fn use_route(cx: &ScopeState) -> &UseRoute {
+    &cx.use_hook(|_| {
+        let router = cx
+            .consume_context::<RouterService>()
+            .expect("Cannot call use_route outside the scope of a Router component");
+
+        router.subscribe_onchange(cx.scope_id());
+
+        UseRouteListener {
+            router: UseRoute { router },
+            scope: cx.scope_id(),
+        }
+    })
+    .router
+}
+
+// The entire purpose of this struct is to unubscribe this component when it is unmounted.
+// The UseRoute can be cloned into async contexts, so we can't rely on its drop to unubscribe.
+// Instead, we hide the drop implementation on this private type exclusive to the hook,
+// and reveal our cached version of UseRoute to the component.
+struct UseRouteListener {
+    router: UseRoute,
+    scope: ScopeId,
+}
+impl Drop for UseRouteListener {
+    fn drop(&mut self) {
+        self.router.router.unsubscribe_onchange(self.scope)
+    }
 }

+ 20 - 1
packages/router/src/service.rs

@@ -1,7 +1,7 @@
 use gloo::history::{BrowserHistory, History, HistoryListener, Location};
 use std::{
     cell::{Cell, Ref, RefCell},
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     rc::Rc,
 };
 
@@ -12,6 +12,7 @@ pub struct RouterService {
     pub(crate) pending_events: Rc<RefCell<Vec<RouteEvent>>>,
     history: Rc<RefCell<BrowserHistory>>,
     slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
+    onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
     root_found: Rc<Cell<Option<ScopeId>>>,
     cur_path_params: Rc<RefCell<HashMap<String, String>>>,
     listener: HistoryListener,
@@ -42,6 +43,7 @@ impl RouterService {
         let location = history.location();
         let path = location.path();
 
+        let onchange_listeners = Rc::new(RefCell::new(HashSet::new()));
         let slots: Rc<RefCell<Vec<(ScopeId, String)>>> = Default::default();
         let pending_events: Rc<RefCell<Vec<RouteEvent>>> = Default::default();
         let root_found = Rc::new(Cell::new(None));
@@ -51,6 +53,7 @@ impl RouterService {
             let regen_route = regen_route.clone();
             let root_found = root_found.clone();
             let slots = slots.clone();
+            let onchange_listeners = onchange_listeners.clone();
             move || {
                 root_found.set(None);
                 // checking if the route is valid is cheap, so we do it
@@ -59,6 +62,11 @@ impl RouterService {
                     regen_route(*slot);
                 }
 
+                for listener in onchange_listeners.borrow_mut().iter() {
+                    log::trace!("regenerating listener {:?}", listener);
+                    regen_route(*listener);
+                }
+
                 // also regenerate the root
                 regen_route(root_scope);
 
@@ -73,6 +81,7 @@ impl RouterService {
             regen_route,
             slots,
             pending_events,
+            onchange_listeners,
             cur_path_params: Rc::new(RefCell::new(HashMap::new())),
         }
     }
@@ -143,6 +152,16 @@ impl RouterService {
     pub fn current_path_params(&self) -> Ref<HashMap<String, String>> {
         self.cur_path_params.borrow()
     }
+
+    pub fn subscribe_onchange(&self, id: ScopeId) {
+        log::trace!("Subscribing onchange for scope id {:?}", id);
+        self.onchange_listeners.borrow_mut().insert(id);
+    }
+
+    pub fn unsubscribe_onchange(&self, id: ScopeId) {
+        log::trace!("Subscribing onchange for scope id {:?}", id);
+        self.onchange_listeners.borrow_mut().remove(&id);
+    }
 }
 
 fn clean_route(route: String) -> String {