瀏覽代碼

feat: use future fully figured out

Jonathan Kelley 3 年之前
父節點
當前提交
9211e1fc78
共有 7 個文件被更改,包括 475 次插入342 次删除
  1. 13 25
      examples/dog_app.rs
  2. 3 3
      examples/suspense.rs
  3. 2 2
      examples/tasks.rs
  4. 1 1
      packages/core/src/scopes.rs
  5. 182 0
      packages/hooks/docs/old.md
  6. 92 291
      packages/hooks/src/usecoroutine.rs
  7. 182 20
      packages/hooks/src/usefuture.rs

+ 13 - 25
examples/dog_app.rs

@@ -16,7 +16,9 @@ struct ListBreeds {
 }
 
 fn app(cx: Scope) -> Element {
-    let breeds = use_future(&cx, || async move {
+    let (breed, set_breed) = use_state(&cx, || None);
+
+    let breeds = use_future(&cx, (), |_| async move {
         reqwest::get("https://dog.ceo/api/breeds/list/all")
             .await
             .unwrap()
@@ -24,13 +26,10 @@ fn app(cx: Scope) -> Element {
             .await
     });
 
-    let (breed, set_breed) = use_state(&cx, || None);
-
     match breeds.value() {
         Some(Ok(breeds)) => cx.render(rsx! {
             div {
-                h1 {"Select a dog breed!"}
-
+                h1 { "Select a dog breed!" }
                 div { display: "flex",
                     ul { flex: "50%",
                         breeds.message.keys().map(|breed| rsx!(
@@ -51,34 +50,23 @@ fn app(cx: Scope) -> Element {
                 }
             }
         }),
-        Some(Err(_e)) => cx.render(rsx! {
-            div { "Error fetching breeds" }
-        }),
-        None => cx.render(rsx! {
-            div { "Loading dogs..." }
-        }),
+        Some(Err(_e)) => cx.render(rsx! { div { "Error fetching breeds" } }),
+        None => cx.render(rsx! { div { "Loading dogs..." } }),
     }
 }
 
+#[derive(serde::Deserialize, Debug)]
+struct DogApi {
+    message: String,
+}
+
 #[inline_props]
 fn Breed(cx: Scope, breed: String) -> Element {
-    #[derive(serde::Deserialize, Debug)]
-    struct DogApi {
-        message: String,
-    }
-
-    let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed);
-
-    let fut = use_future(&cx, || async move {
+    let fut = use_future(&cx, (breed,), |(breed,)| async move {
+        let endpoint = format!("https://dog.ceo/api/breed/{}/images/random", breed);
         reqwest::get(endpoint).await.unwrap().json::<DogApi>().await
     });
 
-    let (name, set_name) = use_state(&cx, || breed.clone());
-    if name != breed {
-        set_name(breed.clone());
-        fut.restart();
-    }
-
     cx.render(match fut.value() {
         Some(Ok(resp)) => rsx! {
             button {

+ 3 - 3
examples/suspense.rs

@@ -35,8 +35,8 @@ fn app(cx: Scope) -> Element {
         div {
             h1 {"Dogs are very important"}
             p {
-                "The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])" 
-                "is a domesticated descendant of the wolf which is characterized by an upturning tail." 
+                "The dog or domestic dog (Canis familiaris[4][5] or Canis lupus familiaris[5])"
+                "is a domesticated descendant of the wolf which is characterized by an upturning tail."
                 "The dog derived from an ancient, extinct wolf,[6][7] and the modern grey wolf is the"
                 "dog's nearest living relative.[8] The dog was the first species to be domesticated,[9][8]"
                 "by hunter–gatherers over 15,000 years ago,[7] before the development of agriculture.[1]"
@@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element {
 /// Suspense is achieved my moving the future into only the component that
 /// actually renders the data.
 fn Doggo(cx: Scope) -> Element {
-    let fut = use_future(&cx, || async move {
+    let fut = use_future(&cx, (), |_| async move {
         reqwest::get("https://dog.ceo/api/breeds/image/random/")
             .await
             .unwrap()

+ 2 - 2
examples/tasks.rs

@@ -12,8 +12,8 @@ fn main() {
 fn app(cx: Scope) -> Element {
     let (count, set_count) = use_state(&cx, || 0);
 
-    use_future(&cx, move || {
-        let set_count = set_count.to_owned();
+    use_future(&cx, (), move |_| {
+        let set_count = set_count.clone();
         async move {
             loop {
                 tokio::time::sleep(Duration::from_millis(1000)).await;

+ 1 - 1
packages/core/src/scopes.rs

@@ -748,7 +748,7 @@ impl ScopeState {
     }
 
     // todo: attach some state to the future to know if we should poll it
-    pub fn remove_future(&self, id: TaskId) {
+    pub fn cancel_future(&self, id: TaskId) {
         self.tasks.remove_fut(id);
     }
 

+ 182 - 0
packages/hooks/docs/old.md

@@ -0,0 +1,182 @@
+
+mod use2 {
+    #![allow(missing_docs)]
+
+    use dioxus_core::{ScopeState, TaskId};
+    use std::{
+        any::Any,
+        cell::{Cell, RefCell},
+        future::Future,
+        rc::Rc,
+    };
+
+    /// A future that resolves to a value.
+    ///
+    /// This runs the future only once - though the future may be regenerated
+    /// through the [`UseFuture::restart`] method.
+    ///
+    /// This is commonly used for components that cannot be rendered until some
+    /// asynchronous operation has completed.
+    ///
+    ///
+    ///
+    ///
+    ///
+    pub fn use_future<'a>(
+        // pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static>(
+        cx: &'a ScopeState,
+    ) -> &'a UseFuture<()> {
+        //     let state = cx.use_hook(move |_| UseFuture {
+        //         update: cx.schedule_update(),
+        //         needs_regen: Cell::new(true),
+        //         slot: Rc::new(Cell::new(None)),
+        //         value: None,
+        //         task: None,
+        //         pending: true,
+        //         dep_cont: Cell::new(0),
+        //         deps: RefCell::new(Vec::new()),
+        //         first_time: true,
+        //     });
+
+        //     if let Some(value) = state.slot.take() {
+        //         state.value = Some(value);
+        //         state.task = None;
+        //     }
+
+        //     if state.needs_regen.get() {
+        //         // We don't need regen anymore
+        //         state.needs_regen.set(false);
+        //         state.pending = false;
+
+        //         // Create the new future
+        //         let fut = new_fut();
+
+        //         // Clone in our cells
+        //         let slot = state.slot.clone();
+        //         let updater = state.update.clone();
+
+        //         state.task = Some(cx.push_future(async move {
+        //             let res = fut.await;
+        //             slot.set(Some(res));
+        //             updater();
+        //         }));
+        //     }
+
+        //     state.first_time = false;
+
+        //     state
+
+        // new_fut: impl FnOnce() -> F,
+
+        todo!()
+    }
+
+    pub enum FutureState<'a, T> {
+        Pending,
+        Complete(&'a T),
+        Regenerating(&'a T), // the old value
+    }
+
+    pub struct UseFuture<T> {
+        update: Rc<dyn Fn()>,
+        needs_regen: Cell<bool>,
+        value: Option<T>,
+        slot: Rc<Cell<Option<T>>>,
+        task: Option<TaskId>,
+        pending: bool,
+        deps: RefCell<Vec<Box<dyn Any>>>,
+        dep_cont: Cell<usize>,
+        first_time: bool,
+    }
+
+    impl<T> UseFuture<T> {
+        pub fn restart(&self) {
+            self.needs_regen.set(true);
+            (self.update)();
+        }
+
+        // clears the value in the future slot without starting the future over
+        pub fn clear(&self) -> Option<T> {
+            (self.update)();
+            self.slot.replace(None)
+        }
+
+        // Manually set the value in the future slot without starting the future over
+        pub fn set(&self, new_value: T) {
+            self.slot.set(Some(new_value));
+            self.needs_regen.set(true);
+            (self.update)();
+        }
+
+        pub fn value(&self) -> Option<&T> {
+            self.value.as_ref()
+        }
+
+        pub fn state(&self) -> FutureState<T> {
+            // self.value.as_ref()
+            FutureState::Pending
+        }
+
+        pub fn build<F>(&self, new_fut: impl FnOnce() -> F) {}
+    }
+
+    #[test]
+    fn test_use_future_deps() {
+        use dioxus_core::prelude::*;
+
+        struct MyProps {
+            val: String,
+            name: i32,
+        }
+
+        fn app(cx: Scope<MyProps>) -> Element {
+            let MyProps { val, name } = cx.props;
+
+            // let val = use_c(&cx)
+            //     .use_dep(val)
+            //     .restart_if(|| false)
+            //     .use_dep(name)
+            //     .build(|(val, name)| async move {});
+
+            // async fn fetch_thing(name: String, num: i32) -> String {
+            //     format!("{} {}", name, num)
+            // }
+
+            // let val = use_future(&cx, || fetch_thing(val.clone(), *name))
+            //     .with_dep(val)
+            //     .with_dep(name)
+            //     .restart_if(|| false);
+
+            None
+        }
+    }
+
+    // pub struct CoroutineBuilder<'a, const PARAM: usize> {
+    //     deps: Vec<Box<dyn Any>>,
+    //     cx: &'a ScopeState,
+    // }
+
+    // macro_rules! dep_impl {
+    //     ($id1:literal to $id2:literal) => {
+    //         impl<'a> CoroutineBuilder<'a, $id1> {
+    //             pub fn use_dep<F: 'static + PartialEq + Clone>(
+    //                 mut self,
+    //                 dep: &F,
+    //             ) -> CoroutineBuilder<'a, $id2> {
+    //                 self.deps.push(Box::new(dep.clone()));
+    //                 unsafe { std::mem::transmute(self) }
+    //             }
+    //         }
+    //     };
+    // }
+
+    // dep_impl!(0 to 1);
+    // dep_impl!(1 to 2);
+    // dep_impl!(2 to 3);
+    // dep_impl!(3 to 4);
+    // dep_impl!(4 to 5);
+    // dep_impl!(5 to 6);
+    // dep_impl!(6 to 7);
+    // dep_impl!(7 to 8);
+    // dep_impl!(8 to 9);
+}

+ 92 - 291
packages/hooks/src/usecoroutine.rs

@@ -9,95 +9,82 @@ use std::{cell::Cell, rc::Rc};
 
 /// Maintain a handle over a future that can be paused, resumed, and canceled.
 ///
-/// This is an upgraded form of use_future with lots of bells-and-whistles.
+/// This is an upgraded form of [`use_future`] with lots of bells-and-whistles.
 ///
+/// [`use_coroutine`] is well suited for long-running tasks and is very customizable.
 ///
 ///
+/// ## Long running tasks
 ///
 ///
 ///
+/// ## One-off tasks
 ///
 ///
+/// ## Cancellation
 ///
 ///
-///
-///
-///
-///
-///
-///
-pub fn use_coroutine<'a, O: 'static, M: 'static>(
-    cx: &'a ScopeState,
-) -> UseCoroutineBuilder<'a, O, M> {
-    let inner = cx.use_hook(|_| {
+/// ## Global State
+#[allow(clippy::mut_from_ref)]
+pub fn use_coroutine<O: 'static>(cx: &ScopeState) -> &mut UseCoroutine<O, ()> {
+    cx.use_hook(|_| {
         //
         UseCoroutine {
             val: Cell::new(None),
             rx: Cell::new(None),
             tx: None,
+            first_run: true,
+            deps: vec![],
+            dep_cnt: 0,
+            needs_regen: false,
+            auto_start: true,
         }
-    });
-
-    UseCoroutineBuilder { cx, inner }
+    })
 }
 
-pub struct UseCoroutineBuilder<'a, O, M = ()> {
-    cx: &'a ScopeState,
-    inner: &'a mut UseCoroutine<O, M>,
+pub struct UseCoroutine<O, M = ()> {
+    val: Cell<Option<O>>,
+    rx: Cell<Option<UnboundedReceiver<M>>>,
+    tx: Option<UnboundedSender<M>>,
+    first_run: bool,
+    deps: Vec<Box<dyn Any>>,
+    dep_cnt: usize,
+    needs_regen: bool,
+    auto_start: bool,
 }
 
-impl<'a, O: 'static, M> UseCoroutineBuilder<'a, O, M> {
-    // fn with_channel<I>(self) -> UseCoroutineBuilder<'a, O, I> {
-    //     UseCoroutineBuilder {
-    //         cx: self.cx,
-    //         inner: self.inner,
-    //     }
-    // }
+pub enum FutureState<'a, T> {
+    Pending,
+    Complete(&'a T),
+    Regenerating(&'a T), // the old value
+}
 
-    fn build<F: Future<Output = O>>(mut self, f: impl FnOnce() -> F) -> &'a UseCoroutine<O, ()> {
-        todo!()
-    }
-    fn build_channel<F: Future<Output = O>>(
-        mut self,
-        f: impl FnOnce(UnboundedReceiver<M>) -> F,
-    ) -> &'a UseCoroutine<O, M> {
+impl<O> UseCoroutine<O, ()> {
+    /// explicitly set the type of the channel used by the coroutine
+    fn with_channel<S>(&mut self) -> &mut UseCoroutine<O, S> {
+        if self.first_run {
+            // self.provide_context()
+        }
         todo!()
     }
 
-    pub fn use_dep(mut self) -> Self {
-        self
-    }
-
-    /// Provide the channel to downstream consumers
-    pub fn provide_context(mut self) -> Self {
-        self
-    }
-
-    pub fn auto_start(mut self, start: bool) -> Self {
-        // if start && self.inner.run_count.get() == 1 {
-        //     self.start();
-        // }
-        self
+    /// explicitly set the type of the channel used by the coroutine
+    fn with_channel_isolate<S>(&mut self) -> &mut UseCoroutine<O, S> {
+        todo!()
     }
 }
 
-pub struct UseCoroutine<O, M = ()> {
-    val: Cell<Option<O>>,
-    rx: Cell<Option<UnboundedReceiver<M>>>,
-    tx: Option<UnboundedSender<M>>,
-}
-
 impl<O, M> UseCoroutine<O, M> {
     pub fn is_running(&self) -> bool {
         false
-        // self.inner.running.get()
+        // self.running.get()
     }
 
     pub fn start(&self) {
         // if !self.is_running() {
         //     if let Some(mut fut) = self.create_fut.take() {
         //         let fut = fut();
-        //         let ready_handle = self.inner.running.clone();
+        //         let ready_handle = self.running.clone();
 
         //         let task = self.cx.push_future(async move {
         //             ready_handle.set(true);
@@ -105,7 +92,7 @@ impl<O, M> UseCoroutine<O, M> {
         //             ready_handle.set(false);
         //         });
 
-        //         self.inner.task_id.set(Some(task));
+        //         self.task_id.set(Some(task));
         //     }
         // }
     }
@@ -121,11 +108,55 @@ impl<O, M> UseCoroutine<O, M> {
     // todo: wire these up, either into the task system or into the coroutine system itself
     // we would have change how we poll the coroutine and how its awaken
 
+    fn build<F: Future<Output = O>>(&mut self, f: impl FnOnce(UnboundedReceiver<M>) -> F) -> &Self {
+        self.first_run = false;
+        if self.auto_start || self.needs_regen {
+            //
+        }
+
+        self
+    }
+
+    pub fn auto_start(mut self, start: bool) -> Self {
+        // if start && self.run_count.get() == 1 {
+        //     self.start();
+        // }
+        self
+    }
+
+    /// Add this value to the dependency list
+    ///
+    /// This is a hook and should be called during the initial hook process.
+    /// It should •not• be called in a conditional.
+    pub fn with_dep<F: 'static + PartialEq + Clone>(&mut self, dependency: &F) -> &mut Self {
+        if let Some(dep) = self.deps.get_mut(self.dep_cnt) {
+            if let Some(saved_dep) = dep.downcast_mut::<F>() {
+                if dependency != saved_dep {
+                    *saved_dep = dependency.clone();
+                    self.needs_regen = true;
+                }
+            };
+        } else {
+            self.deps.push(Box::new(dependency.to_owned()));
+            self.needs_regen = true;
+        }
+
+        self
+    }
+
+    pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self {
+        self
+    }
+
     // pub fn resume(&self) {}
     // pub fn stop(&self) {}
     // pub fn restart(&self) {}
 }
 
+pub struct CoroutineContext<T> {
+    tx: UnboundedSender<T>,
+}
+
 #[cfg(test)]
 mod tests {
     #![allow(unused)]
@@ -135,246 +166,16 @@ mod tests {
     use dioxus_core::prelude::*;
     use futures_util::StreamExt;
 
-    fn app(cx: Scope) -> Element {
-        let poll_tasks = use_coroutine(&cx).auto_start(false).build(|| async {
-            loop {
-                println!("polling tasks");
-            }
-        });
-
-        todo!()
-    }
-
-    fn app_with_channel(cx: Scope) -> Element {
-        // let poll_tasks = use_coroutine(&cx).build_channel(|mut rx| async move {
-        //     while let Some(msg) = rx.next().await {
-        //         println!("polling tasks: {}", msg);
-        //     }
-        // });
-
-        let poll_tasks =
-            use_coroutine(&cx).build_channel(|mut rx: UnboundedReceiver<()>| async move {
+    fn app(cx: Scope, name: String) -> Element {
+        let task = use_coroutine(&cx)
+            .with_dep(&name)
+            .with_channel::<i32>()
+            .build(|mut rx| async move {
                 while let Some(msg) = rx.next().await {
-                    println!("polling tasks: {:?}", msg);
+                    println!("got message: {}", msg);
                 }
             });
 
-        // poll_tasks.send(10);
-
-        todo!()
-    }
-}
-
-mod use2 {
-    #![allow(missing_docs)]
-
-    use dioxus_core::{ScopeState, TaskId};
-    use std::{
-        any::Any,
-        cell::{Cell, RefCell},
-        future::Future,
-        rc::Rc,
-    };
-
-    /// A future that resolves to a value.
-    ///
-    /// This runs the future only once - though the future may be regenerated
-    /// through the [`UseFuture::restart`] method.
-    ///
-    /// This is commonly used for components that cannot be rendered until some
-    /// asynchronous operation has completed.
-    ///
-    ///
-    ///
-    ///
-    ///
-    pub fn use_future<'a>(
-        // pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static>(
-        cx: &'a ScopeState,
-    ) -> &'a UseFuture<()> {
-        //     let state = cx.use_hook(move |_| UseFuture {
-        //         update: cx.schedule_update(),
-        //         needs_regen: Cell::new(true),
-        //         slot: Rc::new(Cell::new(None)),
-        //         value: None,
-        //         task: None,
-        //         pending: true,
-        //         dep_cont: Cell::new(0),
-        //         deps: RefCell::new(Vec::new()),
-        //         first_time: true,
-        //     });
-
-        //     if let Some(value) = state.slot.take() {
-        //         state.value = Some(value);
-        //         state.task = None;
-        //     }
-
-        //     if state.needs_regen.get() {
-        //         // We don't need regen anymore
-        //         state.needs_regen.set(false);
-        //         state.pending = false;
-
-        //         // Create the new future
-        //         let fut = new_fut();
-
-        //         // Clone in our cells
-        //         let slot = state.slot.clone();
-        //         let updater = state.update.clone();
-
-        //         state.task = Some(cx.push_future(async move {
-        //             let res = fut.await;
-        //             slot.set(Some(res));
-        //             updater();
-        //         }));
-        //     }
-
-        //     state.first_time = false;
-
-        //     state
-
-        // new_fut: impl FnOnce() -> F,
-
-        todo!()
-    }
-
-    pub enum FutureState<'a, T> {
-        Pending,
-        Complete(&'a T),
-        Regenerating(&'a T), // the old value
-    }
-
-    pub struct UseFuture<T> {
-        update: Rc<dyn Fn()>,
-        needs_regen: Cell<bool>,
-        value: Option<T>,
-        pending: bool,
-        slot: Rc<Cell<Option<T>>>,
-        task: Option<TaskId>,
-        deps: RefCell<Vec<Box<dyn Any>>>,
-        dep_cont: Cell<usize>,
-        first_time: bool,
-    }
-
-    impl<T> UseFuture<T> {
-        pub fn restart(&self) {
-            self.needs_regen.set(true);
-            (self.update)();
-        }
-
-        // clears the value in the future slot without starting the future over
-        pub fn clear(&self) -> Option<T> {
-            (self.update)();
-            self.slot.replace(None)
-        }
-
-        // Manually set the value in the future slot without starting the future over
-        pub fn set(&self, new_value: T) {
-            self.slot.set(Some(new_value));
-            self.needs_regen.set(true);
-            (self.update)();
-        }
-
-        pub fn value(&self) -> Option<&T> {
-            self.value.as_ref()
-        }
-
-        pub fn state(&self) -> FutureState<T> {
-            // self.value.as_ref()
-            FutureState::Pending
-        }
-
-        /// Add this value to the dependency list
-        ///
-        /// This is a hook and should be called during the initial hook process.
-        /// It should •not• be called in a conditional.
-        pub fn use_dep<F: 'static + PartialEq + Clone>(&self, dependency: &F) -> &Self {
-            let count = self.dep_cont.get();
-            let mut deps = self.deps.borrow_mut();
-
-            match deps.get_mut(count) {
-                Some(dep) => match dep.downcast_mut::<F>() {
-                    Some(saved_dep) => {
-                        if dependency != saved_dep {
-                            *saved_dep = dependency.clone();
-                            self.needs_regen.set(true);
-                        }
-                    }
-                    None => {
-                        if cfg!(debug_assertions) {
-                            panic!("Tried to use a dependency for use_future outside of the use_future hook.");
-                        }
-                    }
-                },
-                None => deps.push(Box::new(dependency.to_owned())),
-            }
-
-            self
-        }
-
-        pub fn restart_if(&self, f: impl FnOnce() -> bool) -> &Self {
-            self
-        }
-
-        pub fn build<F>(&self, new_fut: impl FnOnce() -> F) {}
+        None
     }
-
-    #[test]
-    fn test_use_future_deps() {
-        use dioxus_core::prelude::*;
-
-        struct MyProps {
-            val: String,
-            name: i32,
-        }
-
-        fn app(cx: Scope<MyProps>) -> Element {
-            let MyProps { val, name } = cx.props;
-
-            // let val = use_c(&cx)
-            //     .use_dep(val)
-            //     .restart_if(|| false)
-            //     .use_dep(name)
-            //     .build(|(val, name)| async move {});
-
-            // async fn fetch_thing(name: String, num: i32) -> String {
-            //     format!("{} {}", name, num)
-            // }
-
-            // let val = use_future(&cx, || fetch_thing(val.clone(), *name))
-            //     .with_dep(val)
-            //     .with_dep(name)
-            //     .restart_if(|| false);
-
-            None
-        }
-    }
-
-    // pub struct CoroutineBuilder<'a, const PARAM: usize> {
-    //     deps: Vec<Box<dyn Any>>,
-    //     cx: &'a ScopeState,
-    // }
-
-    // macro_rules! dep_impl {
-    //     ($id1:literal to $id2:literal) => {
-    //         impl<'a> CoroutineBuilder<'a, $id1> {
-    //             pub fn use_dep<F: 'static + PartialEq + Clone>(
-    //                 mut self,
-    //                 dep: &F,
-    //             ) -> CoroutineBuilder<'a, $id2> {
-    //                 self.deps.push(Box::new(dep.clone()));
-    //                 unsafe { std::mem::transmute(self) }
-    //             }
-    //         }
-    //     };
-    // }
-
-    // dep_impl!(0 to 1);
-    // dep_impl!(1 to 2);
-    // dep_impl!(2 to 3);
-    // dep_impl!(3 to 4);
-    // dep_impl!(4 to 5);
-    // dep_impl!(5 to 6);
-    // dep_impl!(6 to 7);
-    // dep_impl!(7 to 8);
-    // dep_impl!(8 to 9);
 }

+ 182 - 20
packages/hooks/src/usefuture.rs

@@ -1,42 +1,54 @@
 use dioxus_core::{ScopeState, TaskId};
-use std::{cell::Cell, future::Future, rc::Rc};
+use std::{any::Any, cell::Cell, future::Future, rc::Rc};
 
-pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static>(
+/// A hook that provides a future that will resolve to a value.
+///
+/// Whenever the hooks dependencies change, the future will be re-evaluated.
+/// If a future is pending when the dependencies change, the previous future
+/// will be allowed to continue
+///
+///
+/// - dependencies: a tuple of references to values that are PartialEq + Clone
+pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static, D: UseFutureDep>(
     cx: &'a ScopeState,
-    new_fut: impl FnOnce() -> F,
+    dependencies: D,
+    future: impl FnOnce(D::Out) -> F,
 ) -> &'a UseFuture<T> {
-    let state = cx.use_hook(move |_| {
-        //
-        UseFuture {
-            update: cx.schedule_update(),
-            needs_regen: Cell::new(true),
-            slot: Rc::new(Cell::new(None)),
-            value: None,
-            task: None,
-        }
+    let state = cx.use_hook(move |_| UseFuture {
+        update: cx.schedule_update(),
+        needs_regen: Cell::new(true),
+        slot: Rc::new(Cell::new(None)),
+        value: None,
+        task: Cell::new(None),
+        dependencies: Vec::new(),
     });
 
     if let Some(value) = state.slot.take() {
         state.value = Some(value);
-        state.task = None;
+        state.task.set(None);
     }
 
-    if state.needs_regen.get() {
+    if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
         // We don't need regen anymore
         state.needs_regen.set(false);
 
         // Create the new future
-        let fut = new_fut();
+        let fut = future(dependencies.out());
 
         // Clone in our cells
         let slot = state.slot.clone();
-        let updater = state.update.clone();
+        let schedule_update = state.update.clone();
+
+        // Cancel the current future
+        if let Some(current) = state.task.take() {
+            cx.cancel_future(current);
+        }
 
-        state.task = Some(cx.push_future(async move {
+        state.task.set(Some(cx.push_future(async move {
             let res = fut.await;
             slot.set(Some(res));
-            updater();
-        }));
+            schedule_update();
+        })));
     }
 
     state
@@ -47,15 +59,33 @@ pub struct UseFuture<T> {
     needs_regen: Cell<bool>,
     value: Option<T>,
     slot: Rc<Cell<Option<T>>>,
-    task: Option<TaskId>,
+    task: Cell<Option<TaskId>>,
+    dependencies: Vec<Box<dyn Any>>,
+}
+
+pub enum UseFutureState<'a, T> {
+    Pending,
+    Complete(&'a T),
+    Reloading(&'a T),
 }
 
 impl<T> UseFuture<T> {
+    /// Restart the future with new dependencies.
+    ///
+    /// Will not cancel the previous future, but will ignore any values that it
+    /// generates.
     pub fn restart(&self) {
         self.needs_regen.set(true);
         (self.update)();
     }
 
+    /// Forcefully cancel a future
+    pub fn cancel(&self, cx: &ScopeState) {
+        if let Some(task) = self.task.take() {
+            cx.cancel_future(task);
+        }
+    }
+
     // clears the value in the future slot without starting the future over
     pub fn clear(&self) -> Option<T> {
         (self.update)();
@@ -69,7 +99,139 @@ impl<T> UseFuture<T> {
         (self.update)();
     }
 
+    /// Return any value, even old values if the future has not yet resolved.
+    ///
+    /// If the future has never completed, the returned value will be `None`.
     pub fn value(&self) -> Option<&T> {
         self.value.as_ref()
     }
+
+    /// Get the ID of the future in Dioxus' internal scheduler
+    pub fn task(&self) -> Option<TaskId> {
+        self.task.get()
+    }
+
+    /// Get the current stateof the future.
+    pub fn state(&self) -> UseFutureState<T> {
+        match (&self.task.get(), &self.value) {
+            // If we have a task and an existing value, we're reloading
+            (Some(_), Some(val)) => UseFutureState::Reloading(val),
+
+            // no task, but value - we're done
+            (None, Some(val)) => UseFutureState::Complete(val),
+
+            // no task, no value - something's wrong? return pending
+            (None, None) => UseFutureState::Pending,
+
+            // Task, no value - we're still pending
+            (Some(_), None) => UseFutureState::Pending,
+        }
+    }
+}
+
+pub trait UseFutureDep: Sized + Clone {
+    type Out;
+    fn out(&self) -> Self::Out;
+    fn apply(self, state: &mut Vec<Box<dyn Any>>) -> bool;
+}
+
+impl UseFutureDep for () {
+    type Out = ();
+    fn out(&self) -> Self::Out {}
+    fn apply(self, _state: &mut Vec<Box<dyn Any>>) -> bool {
+        false
+    }
+}
+
+pub trait Dep: 'static + PartialEq + Clone {}
+impl<T> Dep for T where T: 'static + PartialEq + Clone {}
+
+macro_rules! impl_dep {
+    (
+        $($el:ident=$name:ident,)*
+    ) => {
+        impl< $($el),* > UseFutureDep for ($(&$el,)*)
+        where
+            $(
+                $el: Dep
+            ),*
+        {
+            type Out = ($($el,)*);
+
+            fn out(&self) -> Self::Out {
+                let ($($name,)*) = self;
+                ($((*$name).clone(),)*)
+            }
+
+            #[allow(unused)]
+            fn apply(self, state: &mut Vec<Box<dyn Any>>) -> bool {
+                let ($($name,)*) = self;
+                let mut idx = 0;
+                let mut needs_regen = false;
+
+                $(
+                    match state.get_mut(idx).map(|f| f.downcast_mut::<$el>()).flatten() {
+                        Some(val) => {
+                            if *val != *$name {
+                                *val = $name.clone();
+                                needs_regen = true;
+                            }
+                        }
+                        None => {
+                            state.push(Box::new($name.clone()));
+                            needs_regen = true;
+                        }
+                    }
+                    idx += 1;
+                )*
+
+                needs_regen
+            }
+        }
+    };
+}
+
+impl_dep!(A = a,);
+impl_dep!(A = a, B = b,);
+impl_dep!(A = a, B = b, C = c,);
+impl_dep!(A = a, B = b, C = c, D = d,);
+impl_dep!(A = a, B = b, C = c, D = d, E = e,);
+impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f,);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[allow(unused)]
+    #[test]
+    fn test_use_future() {
+        use dioxus_core::prelude::*;
+
+        struct MyProps {
+            a: String,
+            b: i32,
+            c: i32,
+            d: i32,
+            e: i32,
+        }
+
+        fn app(cx: Scope<MyProps>) -> Element {
+            // should only ever run once
+            let fut = use_future(&cx, (), |_| async move {
+                //
+            });
+
+            // runs when a is changed
+            let fut = use_future(&cx, (&cx.props.a,), |(a,)| async move {
+                //
+            });
+
+            // runs when a or b is changed
+            let fut = use_future(&cx, (&cx.props.a, &cx.props.b), |(a, b)| async move {
+                //
+            });
+
+            None
+        }
+    }
 }