From 74aa55f85fbaed93f965bae6333c849a5cee81db Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 15 Jan 2024 17:04:39 -0800 Subject: [PATCH] Bring back mroe hooks, remove old hooks, cleanup a number of examples --- Cargo.lock | 1 + examples/compose.rs | 6 +- examples/counter.rs | 2 +- examples/crm.rs | 7 - examples/dog_app.rs | 52 ++-- examples/error_handle.rs | 2 +- examples/eval.rs | 14 +- examples/filedragdrop.rs | 2 +- examples/framework_benchmark.rs | 152 ---------- .../openid_connect_demo/src/views/header.rs | 2 +- examples/optional_props.rs | 14 +- examples/pattern_model.rs | 17 +- examples/pattern_reducer.rs | 10 +- examples/read_size.rs | 2 +- examples/shared_state.rs | 73 ----- examples/shortcut.rs | 2 +- examples/simple_desktop.rs | 4 +- examples/simple_router.rs | 2 +- examples/spread.rs | 1 + examples/ssr.rs | 2 +- examples/streams.rs | 2 +- examples/suspense.rs | 4 +- packages/desktop/examples/stress.rs | 2 +- packages/dioxus/Cargo.toml | 2 +- packages/hooks/Cargo.toml | 1 + packages/hooks/src/lib.rs | 30 +- packages/hooks/src/use_coroutine.rs | 122 ++++---- packages/hooks/src/use_future.rs | 263 +++--------------- packages/hooks/src/use_root_context.rs | 11 +- packages/liveview/examples/axum_stress.rs | 2 +- packages/router-macro/src/lib.rs | 6 +- packages/signals/src/rt.rs | 2 +- packages/signals/src/signal.rs | 8 + 33 files changed, 179 insertions(+), 643 deletions(-) delete mode 100644 examples/framework_benchmark.rs delete mode 100644 examples/shared_state.rs diff --git a/Cargo.lock b/Cargo.lock index 2d31cab18..09a013402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2631,6 +2631,7 @@ dependencies = [ "dioxus", "dioxus-core", "dioxus-debug-cell", + "dioxus-signals", "futures-channel", "futures-util", "slab", diff --git a/examples/compose.rs b/examples/compose.rs index 2410e973f..c725a8ceb 100644 --- a/examples/compose.rs +++ b/examples/compose.rs @@ -22,7 +22,7 @@ fn app() -> Element { dioxus_desktop::window().new_window( VirtualDom::new_with_props(compose, tx.clone()), Default::default(), - ) + ); }; rsx! { @@ -39,7 +39,7 @@ fn app() -> Element { } } -fn compose(receiver: UnboundedSender) -> Element { +fn compose(tx: UnboundedSender) -> Element { let user_input = use_signal(String::new); rsx! { @@ -48,7 +48,7 @@ fn compose(receiver: UnboundedSender) -> Element { button { onclick: move |_| { - cx.props.app_tx.send(user_input.get().clone()); + tx.send(user_input.get().clone()); dioxus_desktop::window().close(); }, "Click to send" diff --git a/examples/counter.rs b/examples/counter.rs index 0be35938a..10fb43773 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -9,7 +9,7 @@ fn main() { fn app() -> Element { let counters = use_signal(|| vec![0, 0, 0]); - let sum = use_selector(move || counters.read().iter().copied().sum() as usize); + let sum = use_selector(move || counters.read().iter().copied().sum::()); render! { div { diff --git a/examples/crm.rs b/examples/crm.rs index 0d3d36fa7..ff4694bab 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -24,12 +24,8 @@ pub struct Client { pub description: String, } -type ClientContext = Vec; - #[component] fn App() -> Element { - use_shared_state_provider::(Default::default); - render! { link { rel: "stylesheet", @@ -54,8 +50,6 @@ fn App() -> Element { #[component] fn ClientList() -> Element { - let clients = use_shared_state::().unwrap(); - rsx! { h2 { "List of Clients" } Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" } @@ -71,7 +65,6 @@ fn ClientList() -> Element { #[component] fn ClientAdd() -> Element { - let clients = use_shared_state::().unwrap(); let first_name = use_signal(String::new); let last_name = use_signal(String::new); let description = use_signal(String::new); diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 5f0f90eb9..410f335f4 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -5,15 +5,10 @@ fn main() { dioxus_desktop::launch(app); } -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] -struct ListBreeds { - message: HashMap>, -} - fn app() -> Element { let breed = use_signal(|| "deerhound".to_string()); - let breeds = use_future(|| async move { + let breed_list = use_future(|| async move { reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -21,13 +16,13 @@ fn app() -> Element { .await }); - match breeds.value()? { - Ok(breed_list) => rsx! { + match breed_list.value().read().as_ref() { + Some(Ok(breeds)) => rsx! { div { height: "500px", h1 { "Select a dog breed!" } div { display: "flex", ul { flex: "50%", - for cur_breed in breed_list.message.keys().take(10) { + for cur_breed in breeds.message.keys().take(10).cloned() { li { key: "{cur_breed}", button { onclick: move |_| breed.set(cur_breed.clone()), "{cur_breed}" } } @@ -37,18 +32,13 @@ fn app() -> Element { } } }, - Err(_e) => rsx! { div { "Error fetching breeds" } }, + _ => rsx! { div { "loading breeds" } }, } } -#[derive(serde::Deserialize, Debug)] -struct DogApi { - message: String, -} - #[component] fn BreedPic(breed: Signal) -> Element { - let fut = use_future(|breed| async move { + let fut = use_future(|| async move { reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random")) .await .unwrap() @@ -56,23 +46,23 @@ fn BreedPic(breed: Signal) -> Element { .await }); - match fut.value()? { - Ok(resp) => render! { + match fut.value().read().as_ref() { + Some(Ok(resp)) => rsx! { div { - button { - onclick: move |_| { - println!("clicked"); - fut.restart() - }, - "Click to fetch another doggo" - } - img { - src: "{resp.message}", - max_width: "500px", - max_height: "500px", - } + button { onclick: move |_| fut.restart(), "Click to fetch another doggo" } + img { max_width: "500px", max_height: "500px", src: "{resp.message}" } } }, - Err(_) => render! { div { "loading dogs failed" } }, + _ => rsx! { div { "loading dog picture" } }, } } + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +struct ListBreeds { + message: HashMap>, +} + +#[derive(serde::Deserialize, Debug)] +struct DogApi { + message: String, +} diff --git a/examples/error_handle.rs b/examples/error_handle.rs index eb340a57f..6f5b6efb5 100644 --- a/examples/error_handle.rs +++ b/examples/error_handle.rs @@ -1,7 +1,7 @@ use dioxus::{core::CapturedError, prelude::*}; fn main() { - dioxus_desktop::launch(App); + dioxus_desktop::launch(app); } fn app() -> Element { diff --git a/examples/eval.rs b/examples/eval.rs index 47a35c81a..5d4677fb9 100644 --- a/examples/eval.rs +++ b/examples/eval.rs @@ -5,8 +5,8 @@ fn main() { } fn app() -> Element { - let future = use_future(|_| async move { - let eval = eval( + let future = use_future(|| async move { + let mut eval = eval( r#" dioxus.send("Hi from JS!"); let msg = await dioxus.recv(); @@ -22,12 +22,8 @@ fn app() -> Element { res }); - match future.value() { - Some(v) => rsx!( - p { "{v}" } - ), - _ => rsx!( - p { "hello" } - ), + match future.value().read().as_ref() { + Some(v) => rsx!( p { "{v}" } ), + _ => rsx!( p { "waiting.." } ), } } diff --git a/examples/filedragdrop.rs b/examples/filedragdrop.rs index 5f873e290..51dacdb58 100644 --- a/examples/filedragdrop.rs +++ b/examples/filedragdrop.rs @@ -10,7 +10,7 @@ fn main() { .launch(app) } -fn app(_props: ()) -> Element { +fn app() -> Element { rsx!( div { h1 { "drag a file here and check your console" } diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs deleted file mode 100644 index 5f01a885f..000000000 --- a/examples/framework_benchmark.rs +++ /dev/null @@ -1,152 +0,0 @@ -#![allow(non_snake_case)] - -use dioxus::prelude::*; -use rand::prelude::*; - -fn main() { - dioxus_desktop::launch(app); -} - -#[derive(Clone, PartialEq)] -struct Label { - key: usize, - labels: [&'static str; 3], -} - -impl Label { - fn new_list(num: usize) -> Vec { - let mut rng = SmallRng::from_entropy(); - let mut labels = Vec::with_capacity(num); - for x in 0..num { - labels.push(Label { - key: x, - labels: [ - ADJECTIVES.choose(&mut rng).unwrap(), - COLOURS.choose(&mut rng).unwrap(), - NOUNS.choose(&mut rng).unwrap(), - ], - }); - } - labels - } -} - -fn app() -> Element { - let items = use_signal(Vec::new); - let selected = use_signal(|| None); - - rsx! { - div { class: "container", - div { class: "jumbotron", - div { class: "row", - div { class: "col-md-6", h1 { "Dioxus" } } - div { class: "col-md-6", - div { class: "row", - ActionButton { name: "Create 1,000 rows", id: "run", - onclick: move |_| items.set(Label::new_list(1_000)), - } - ActionButton { name: "Create 10,000 rows", id: "runlots", - onclick: move |_| items.set(Label::new_list(10_000)), - } - ActionButton { name: "Append 1,000 rows", id: "add", - onclick: move |_| items.write().extend(Label::new_list(1_000)), - } - ActionButton { name: "Update every 10th row", id: "update", - onclick: move |_| items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"), - } - ActionButton { name: "Clear", id: "clear", - onclick: move |_| items.write().clear(), - } - ActionButton { name: "Swap rows", id: "swaprows", - onclick: move |_| items.write().swap(0, 998), - } - } - } - } - } - table { - tbody { - for (id, item) in items.read().iter().enumerate() { - tr { - class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" }, - td { class:"col-md-1" } - td { class:"col-md-1", "{item.key}" } - td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), - a { class: "lbl", "{item.labels[0]}{item.labels[1]}{item.labels[2]}" } - } - td { class: "col-md-1", - a { class: "remove", onclick: move |_| { items.write().remove(id); }, - span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" } - } - } - td { class: "col-md-6" } - } - } - - } - } - span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" } - } - } -} - -#[derive(Props)] -struct ActionButtonProps<'a> { - name: &'a str, - id: &'a str, - onclick: EventHandler<'a>, -} - -fn ActionButton(props: ActionButtonProps) -> Element { - rsx! { - div { - class: "col-sm-6 smallpad", - button { - class:"btn btn-primary btn-block", - r#type: "button", - id: "{cx.props.id}", - onclick: move |_| cx.props.onclick.call(()), - - "{cx.props.name}" - } - } - } -} - -static ADJECTIVES: &[&str] = &[ - "pretty", - "large", - "big", - "small", - "tall", - "short", - "long", - "handsome", - "plain", - "quaint", - "clean", - "elegant", - "easy", - "angry", - "crazy", - "helpful", - "mushy", - "odd", - "unsightly", - "adorable", - "important", - "inexpensive", - "cheap", - "expensive", - "fancy", -]; - -static COLOURS: &[&str] = &[ - "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", - "orange", -]; - -static NOUNS: &[&str] = &[ - "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", - "pizza", "mouse", "keyboard", -]; diff --git a/examples/openid_connect_demo/src/views/header.rs b/examples/openid_connect_demo/src/views/header.rs index 366d5e03f..ec4360bef 100644 --- a/examples/openid_connect_demo/src/views/header.rs +++ b/examples/openid_connect_demo/src/views/header.rs @@ -129,7 +129,7 @@ pub fn RefreshToken(cx: Scope) -> Element { #[component] pub fn LoadClient() -> Element { - let init_client_future = use_future(|_| async move { init_oidc_client().await }); + let init_client_future = use_future(|| async move { init_oidc_client().await }); let fermi_client: &UseAtomRef = use_atom_ref(&FERMI_CLIENT); cx.render(match init_client_future.value() { Some(client_props) => match client_props { diff --git a/examples/optional_props.rs b/examples/optional_props.rs index 1e87230a7..f912e9c07 100644 --- a/examples/optional_props.rs +++ b/examples/optional_props.rs @@ -35,7 +35,7 @@ fn app() -> Element { type SthElse = Option; -#[derive(Props, PartialEq)] +#[derive(Props, PartialEq, Clone)] struct ButtonProps { a: String, @@ -51,14 +51,14 @@ struct ButtonProps { e: SthElse, } -fn Button(cx: Scope) -> Element { +fn Button(props: ButtonProps) -> Element { rsx! { button { - "{cx.props.a} | " - "{cx.props.b:?} | " - "{cx.props.c:?} | " - "{cx.props.d:?} | " - "{cx.props.e:?}" + "{props.a} | " + "{props.b:?} | " + "{props.c:?} | " + "{props.d:?} | " + "{props.e:?}" } } } diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index ca2e967d5..c4e59dff9 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -120,20 +120,17 @@ fn app() -> Element { } } -#[derive(Props)] -struct CalculatorKeyProps { - #[props(into)] - name: String, +#[component] +fn CalculatorKey( + #[props(into)] name: String, onclick: EventHandler, children: Element, -} - -fn CalculatorKey(props: CalculatorKeyProps) -> Element { +) -> Element { rsx! { button { - class: "calculator-key {cx.props.name}", - onclick: move |e| cx.props.onclick.call(e), - {&cx.props.children} + class: "calculator-key {name}", + onclick: move |e| onclick.call(e), + {&children} } } } diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index 55a69b006..5ea8b209e 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -20,13 +20,9 @@ fn app() -> Element { rsx!( div { h1 {"Select an option"} - h3 { "The radio is... ", {state.is_playing()}, "!" } - button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause), - "Pause" - } - button { onclick: move |_| state.make_mut().reduce(PlayerAction::Play), - "Play" - } + h3 { "The radio is... ", {state.read().is_playing()}, "!" } + button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" } + button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" } } ) } diff --git a/examples/read_size.rs b/examples/read_size.rs index bbc81cc8e..16eda6c13 100644 --- a/examples/read_size.rs +++ b/examples/read_size.rs @@ -45,7 +45,7 @@ fn app() -> Element { height: "50%", background_color: "red", onmounted: move |cx| div_element.set(Some(cx.inner().clone())), - "This element is {dimensions.read():?}" + "This element is {*dimensions():?}" } button { diff --git a/examples/shared_state.rs b/examples/shared_state.rs deleted file mode 100644 index 8b8398e0c..000000000 --- a/examples/shared_state.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::collections::HashMap; - -use dioxus::prelude::*; - -fn main() { - dioxus_desktop::launch(App); -} - -#[derive(Default)] -struct CoolData { - data: HashMap, -} - -impl CoolData { - pub fn new(data: HashMap) -> Self { - Self { data } - } - - pub fn view(&self, id: &usize) -> Option<&String> { - self.data.get(id) - } - - pub fn set(&mut self, id: usize, data: String) { - self.data.insert(id, data); - } -} - -#[component] -#[rustfmt::skip] -pub fn App() -> Element { - use_shared_state_provider(|| CoolData::new(HashMap::from([ - (0, "Hello, World!".to_string()), - (1, "Dioxus is amazing!".to_string()) - ]))); - - render!( - DataEditor { - id: 0 - } - DataEditor { - id: 1 - } - DataView { - id: 0 - } - DataView { - id: 1 - } - ) -} - -#[component] -fn DataEditor(id: usize) -> Element { - let data = use_shared_state::()?; - - render! { - p { - {data.read().view(id)?} - } - } -} - -#[component] -fn DataView(id: usize) -> Element { - let data = use_shared_state::()?; - - render! { - input { - oninput: move |e: FormEvent| data.write().set(*id, e.value()), - value: data.read().view(id)? - } - } -} diff --git a/examples/shortcut.rs b/examples/shortcut.rs index 69d3fdb1b..8ff506920 100644 --- a/examples/shortcut.rs +++ b/examples/shortcut.rs @@ -8,7 +8,7 @@ fn main() { fn app() -> Element { let toggled = use_signal(|| false); - use_global_shortcut("ctrl+s", move || toggled.toggle()); + _ = use_global_shortcut("ctrl+s", move || toggled.toggle()); rsx!("toggle: {toggled}") } diff --git a/examples/simple_desktop.rs b/examples/simple_desktop.rs index 770cb6e00..7875d4a62 100644 --- a/examples/simple_desktop.rs +++ b/examples/simple_desktop.rs @@ -54,13 +54,13 @@ fn NavBar() -> Element { #[component] fn Home() -> Element { - log::debug!("rendering home {:?}", cx.scope_id()); + log::debug!("rendering home {:?}", current_scope_id()); render! { h1 { "Home" } } } #[component] fn BlogList() -> Element { - log::debug!("rendering blog list {:?}", cx.scope_id()); + log::debug!("rendering blog list {:?}", current_scope_id()); render! { div { "Blog List" } } } diff --git a/examples/simple_router.rs b/examples/simple_router.rs index aff0afe58..e678571ed 100644 --- a/examples/simple_router.rs +++ b/examples/simple_router.rs @@ -38,5 +38,5 @@ fn Nav() -> Element { } fn main() { - dioxus_desktop::launch(|cx| render!(Router:: {})); + dioxus_desktop::launch(|| render!(Router:: {})); } diff --git a/examples/spread.rs b/examples/spread.rs index ce32dae12..f4f80c896 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -31,6 +31,7 @@ fn Component(props: Props) -> Element { struct Props { #[props(extends = GlobalAttributes)] attributes: Vec, + extra_data: String, extra_data2: String, } diff --git a/examples/ssr.rs b/examples/ssr.rs index f8980a5f3..38dc3b1c1 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -13,7 +13,7 @@ fn main() { // Or we can render rsx! calls themselves println!( "{}", - dioxus_ssr::render_lazy(rsx! { + dioxus_ssr::render_element(rsx! { div { h1 { "Hello, world!" } } diff --git a/examples/streams.rs b/examples/streams.rs index b5b66e541..a11d10d05 100644 --- a/examples/streams.rs +++ b/examples/streams.rs @@ -10,7 +10,7 @@ fn main() { fn app() -> Element { let count = use_signal(|| 10); - use_future(|_| async move { + use_future(|| async move { let mut stream = some_stream(); while let Some(second) = stream.next().await { diff --git a/examples/suspense.rs b/examples/suspense.rs index 8d73201f5..7094264b4 100644 --- a/examples/suspense.rs +++ b/examples/suspense.rs @@ -48,7 +48,7 @@ fn app() -> Element { /// Suspense is achieved my moving the future into only the component that /// actually renders the data. fn Doggo() -> Element { - let fut = use_future(|_| async move { + let fut = use_future(|| async move { #[derive(serde::Deserialize)] struct DogApi { message: String, @@ -61,7 +61,7 @@ fn Doggo() -> Element { .await }); - match fut.value() { + match fut.value().read().as_ref() { Some(Ok(resp)) => rsx! { button { onclick: move |_| fut.restart(), diff --git a/packages/desktop/examples/stress.rs b/packages/desktop/examples/stress.rs index 0d7e27b2b..50181805a 100644 --- a/packages/desktop/examples/stress.rs +++ b/packages/desktop/examples/stress.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; fn app() -> Element { let state = use_signal(|| 0); - use_future(|_| { + use_future(|| { to_owned![state]; async move { loop { diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index 37b0a57b2..82e365972 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -22,7 +22,7 @@ dioxus-signals = { workspace = true, optional = true } dioxus-hot-reload = { workspace = true, optional = true } [features] -default = ["macro", "html", "hot-reload", "signals"] +default = ["macro", "html", "hot-reload", "signals", "hooks"] signals = ["dioxus-signals"] macro = ["dioxus-core-macro", "dioxus-rsx"] html = ["dioxus-html"] diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index bf07389ff..dd3b00f8d 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -15,6 +15,7 @@ nightly-features = [] [dependencies] dioxus-core = { workspace = true } +dioxus-signals = { workspace = true } futures-channel = { workspace = true } tracing = { workspace = true } thiserror = { workspace = true } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 0951b29d5..60e4a48f5 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -55,42 +55,26 @@ macro_rules! to_owned { }; } -// pub mod computed; - mod use_on_destroy; pub use use_on_destroy::*; -// mod use_const; -// pub use use_const::*; - // mod use_context; // pub use use_context::*; -// mod use_state; -// pub use use_state::{use_state, UseState}; +mod use_coroutine; +pub use use_coroutine::*; -// mod use_ref; -// pub use use_ref::*; - -// mod use_shared_state; -// pub use use_shared_state::*; - -// mod use_coroutine; -// pub use use_coroutine::*; - -// mod use_future; -// pub use use_future::*; +mod use_future; +pub use use_future::*; // mod use_effect; // pub use use_effect::*; -// mod use_callback; -// pub use use_callback::*; - // mod use_memo; // pub use use_memo::*; // mod use_on_create; // pub use use_on_create::*; -// mod use_root_context; -// pub use use_root_context::*; + +mod use_root_context; +pub use use_root_context::*; diff --git a/packages/hooks/src/use_coroutine.rs b/packages/hooks/src/use_coroutine.rs index 27144ce2b..1c391744c 100644 --- a/packages/hooks/src/use_coroutine.rs +++ b/packages/hooks/src/use_coroutine.rs @@ -1,4 +1,6 @@ -use dioxus_core::{ScopeState, Task}; +use dioxus_core::prelude::{consume_context, provide_context, push_future, use_hook}; +use dioxus_core::Task; +use dioxus_signals::{CopyValue, Signal}; pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use std::future::Future; @@ -63,102 +65,76 @@ use std::future::Future; /// } /// }) /// ``` -pub fn use_coroutine(nit: G) -> &Coroutine +pub fn use_coroutine(init: G) -> Coroutine where M: 'static, G: FnOnce(UnboundedReceiver) -> F, F: Future + 'static, { - cx.use_hook(|| { + let coroutine = use_hook(|| { + provide_context(Coroutine { + needs_regen: Signal::new(true), + tx: CopyValue::new(None), + task: CopyValue::new(None), + }) + }); + + // We do this here so we can capture data with FnOnce + // this might not be the best API + if *coroutine.needs_regen.read() { let (tx, rx) = futures_channel::mpsc::unbounded(); - let task = cx.push_future(init(rx)); - cx.provide_context(Coroutine { tx, task }) - }) + let task = push_future(init(rx)).unwrap(); + coroutine.tx.set(Some(tx)); + coroutine.task.set(Some(task)); + coroutine.needs_regen.set_untracked(false); + } + + coroutine } /// Get a handle to a coroutine higher in the tree /// /// See the docs for [`use_coroutine`] for more details. #[must_use] -pub fn use_coroutine_handle() -> Option<&Coroutine> { - cx.use_hook(|| cx.consume_context::>()) - .as_ref() +pub fn use_coroutine_handle() -> Option> { + use_hook(|| consume_context::>()) } -pub struct Coroutine { - tx: UnboundedSender, - task: Task, -} - -// for use in futures -impl Clone for Coroutine { - fn clone(&self) -> Self { - Self { - tx: self.tx.clone(), - task: self.task, - } - } +#[derive(PartialEq)] +pub struct Coroutine { + needs_regen: Signal, + tx: CopyValue>>, + task: CopyValue>, } impl Coroutine { - /// Get the ID of this coroutine - #[must_use] - pub fn task_id(&self) -> Task { - self.task + /// Get the underlying task handle + pub fn task(&self) -> Task { + self.task.read().clone().unwrap() } /// Send a message to the coroutine pub fn send(&self, msg: T) { - let _ = self.tx.unbounded_send(msg); + let _ = self.tx.read().as_ref().unwrap().unbounded_send(msg); + } + + /// Restart this coroutine + /// + /// Forces the component to re-render, which will re-invoke the coroutine. + pub fn restart(&self) { + self.needs_regen.set(true); + self.task().stop(); } } -impl PartialEq for Coroutine { - fn eq(&self, other: &Self) -> bool { - self.task == other.task - } -} - -#[cfg(test)] -mod tests { - #![allow(unused)] - - use super::*; - use dioxus_core::prelude::*; - use futures_channel::mpsc::unbounded; - use futures_util::StreamExt; - - fn app(name: String) -> Element { - let task = use_coroutine(|mut rx: UnboundedReceiver| async move { - while let Some(msg) = rx.next().await { - println!("got message: {msg}"); - } - }); - - let task2 = use_coroutine(view_task); - - let task3 = use_coroutine(|rx| complex_task(rx, 10)); - - todo!() - } - - async fn view_task(mut rx: UnboundedReceiver) { - while let Some(msg) = rx.next().await { - println!("got message: {msg}"); - } - } - - enum Actions { - CloseAll, - OpenAll, - } - - async fn complex_task(mut rx: UnboundedReceiver, name: i32) { - while let Some(msg) = rx.next().await { - match msg { - Actions::CloseAll => todo!(), - Actions::OpenAll => todo!(), - } +// manual impl since deriving doesn't work with generics +impl Copy for Coroutine {} +impl Clone for Coroutine { + fn clone(&self) -> Self { + Self { + tx: self.tx, + task: self.task, + needs_regen: self.needs_regen, } } } diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 06a61ad14..e6d6cadd4 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -1,9 +1,8 @@ #![allow(missing_docs)] use dioxus_core::{ScopeState, Task}; +use dioxus_signals::{use_effect, use_signal, Signal}; use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc}; -use crate::{use_state, UseState}; - /// A future that resolves to a value. /// /// This runs the future only once - though the future may be regenerated @@ -17,70 +16,29 @@ use crate::{use_state, UseState}; /// will be canceled before the new one is started. /// /// - dependencies: a tuple of references to values that are PartialEq + Clone -pub fn use_future - dependencies: D, - future: impl FnOnce(D::Out) -> F, -) -> &UseFuture +pub fn use_future(future: impl FnMut() -> F) -> UseFuture where T: 'static, F: Future + 'static, - D: UseFutureDep, { - let val = use_signal(|| None); + let task = use_signal(|| None); - let state = cx.use_hook(move || UseFuture { - update: cx.schedule_update(), - needs_regen: Rc::new(Cell::new(true)), - state: val.clone(), - task: Default::default(), + use_effect(|| { + // task.set(); }); + // - let state_dependencies = cx.use_hook(Vec::new); - - if dependencies.clone().apply(state_dependencies) || state.needs_regen.get() { - // kill the old one, if it exists - if let Some(task) = state.task.take() { - cx.remove_future(task); - } - - // Create the new future - let fut = future(dependencies.out()); - let val = val.clone(); - let task = state.task.clone(); - - state.task.set(Some(cx.push_future(async move { - val.set(Some(fut.await)); - task.take(); - }))); - - // Mark that we don't need to regenerate - state.needs_regen.set(false); + UseFuture { + value: todo!(), + task, + state: todo!(), } - - // update the current value - state.state.current_val = val.current_val.clone(); - - state } -pub enum FutureState<'a, T> { - Pending, - Complete(&'a T), - Regenerating(&'a T), // the old value -} - -#[derive(Clone)] pub struct UseFuture { - update: Arc, - needs_regen: Rc>, - task: Rc>>, - state: UseState>, -} - -pub enum UseFutureState<'a, T> { - Pending, - Complete(&'a T), - Reloading(&'a T), + value: Signal, + task: Signal>, + state: Signal>, } impl UseFuture { @@ -89,198 +47,57 @@ impl UseFuture { /// 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)(); + // self.needs_regen.set(true); + // (self.update)(); } /// Forcefully cancel a future - pub fn cancel(&self, ) { - if let Some(task) = self.task.take() { - cx.remove_future(task); - } + pub fn cancel(&self) { + // if let Some(task) = self.task.take() { + // cx.remove_future(task); + // } } // Manually set the value in the future slot without starting the future over pub fn set(&self, new_value: T) { - self.state.set(Some(new_value)); + // self.state.set(Some(new_value)); } /// 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.state.current_val.as_ref().as_ref() + pub fn value(&self) -> Signal> { + todo!() + // self.state.current_val.as_ref().as_ref() } /// Get the ID of the future in Dioxus' internal scheduler pub fn task(&self) -> Option { - self.task.get() + todo!() + // self.task.get() } /// Get the current state of the future. pub fn state(&self) -> UseFutureState { - match (&self.task.get(), &self.value()) { - // If we have a task and an existing value, we're reloading - (Some(_), Some(val)) => UseFutureState::Reloading(val), + todo!() + // 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, but value - we're done + // (None, Some(val)) => UseFutureState::Complete(val), - // no task, no value - something's wrong? return pending - (None, None) => UseFutureState::Pending, + // // no task, no value - something's wrong? return pending + // (None, None) => UseFutureState::Pending, - // Task, no value - we're still pending - (Some(_), 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>) -> bool; -} - -impl UseFutureDep for () { - type Out = (); - fn out(&self) -> Self::Out {} - fn apply(self, _state: &mut Vec>) -> bool { - false - } -} - -pub trait Dep: 'static + PartialEq + Clone {} -impl Dep for T where T: 'static + PartialEq + Clone {} - -impl UseFutureDep for &A { - type Out = A; - fn out(&self) -> Self::Out { - (*self).clone() - } - fn apply(self, state: &mut Vec>) -> bool { - match state.get_mut(0).and_then(|f| f.downcast_mut::()) { - Some(val) => { - if *val != *self { - *val = self.clone(); - return true; - } - } - None => { - state.push(Box::new(self.clone())); - return true; - } - } - false - } -} - -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>) -> 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,); -impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g,); -impl_dep!(A = a, B = b, C = c, D = d, E = e, F = f, G = g, H = h,); - -/// A helper macro that merges uses the closure syntax to elaborate the dependency array -#[macro_export] -macro_rules! use_future { - ($cx:ident, || $($rest:tt)*) => { use_future( $cx, (), |_| $($rest)* ) }; - ($cx:ident, | $($args:tt),* | $($rest:tt)*) => { - use_future( - $cx, - ($($args),*), - |($($args),*)| $($rest)* - ) - }; -} - -#[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, - } - - async fn app(cx: Scope<'_, MyProps>) -> Element { - // should only ever run once - let fut = use_future(|_| async move {}); - - // runs when a is changed - let fut = use_future((&cx.props.a,), |(a,)| async move {}); - - // runs when a or b is changed - let fut = use_future((&cx.props.a, &cx.props.b), |(a, b)| async move { 123 }); - - let a = use_future(|| async move { - // do the thing! - }); - - let b = &123; - let c = &123; - - let a = use_future(|b, c| async move { - let a = b + c; - let blah = "asd"; - }); - - todo!() - } - } +pub enum UseFutureState { + Pending, + Complete(Signal), + Regenerating(Signal), // the old value } diff --git a/packages/hooks/src/use_root_context.rs b/packages/hooks/src/use_root_context.rs index 921771395..20e784a5d 100644 --- a/packages/hooks/src/use_root_context.rs +++ b/packages/hooks/src/use_root_context.rs @@ -1,9 +1,10 @@ -use dioxus_core::ScopeState; +use dioxus_core::{prelude::consume_context, prelude::provide_root_context, use_hook}; /// -pub fn use_root_context(, new: impl FnOnce() -> T) -> &T { - cx.use_hook(|| { - cx.consume_context::() - .unwrap_or_else(|| cx.provide_root_context(new())) +pub fn use_root_context(new: impl FnOnce() -> T) -> T { + use_hook(|| { + consume_context::() + // If no context is provided, create a new one at the root + .unwrap_or_else(|| provide_root_context(new()).expect(" A runtime to exist")) }) } diff --git a/packages/liveview/examples/axum_stress.rs b/packages/liveview/examples/axum_stress.rs index 49a0854b5..fbb8fd1cd 100644 --- a/packages/liveview/examples/axum_stress.rs +++ b/packages/liveview/examples/axum_stress.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; fn app() -> Element { let state = use_signal(|| 0); - use_future(|_| { + use_future(|| { to_owned![state]; async move { loop { diff --git a/packages/router-macro/src/lib.rs b/packages/router-macro/src/lib.rs index f6e4855a1..75f739372 100644 --- a/packages/router-macro/src/lib.rs +++ b/packages/router-macro/src/lib.rs @@ -584,12 +584,12 @@ impl RouteEnum { } quote! { - impl dioxus_router::routable::Routable for #name where Self: Clone { - const SITE_MAP: &'static [dioxus_router::routable::SiteMapSegment] = &[ + impl ::dioxus_router::routable::Routable for #name where Self: Clone { + const SITE_MAP: &'static [::dioxus_router::routable::SiteMapSegment] = &[ #(#site_map,)* ]; - fn render<'a>(&self, cx: &'a dioxus::prelude::ScopeState, level: usize) -> dioxus::prelude::Element { + fn render(&self, level: usize) -> ::dioxus::prelude::Element { let myself = self.clone(); match (level, myself) { #(#matches)* diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 18e8b4626..30e4439e0 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -158,7 +158,7 @@ impl CopyValue { } /// Set the value. If the value has been dropped, this will panic. - pub fn set(&mut self, value: T) { + pub fn set(&self, value: T) { *self.write() = value; } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 185ffea16..8e3b70469 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -292,6 +292,14 @@ impl Signal { *self.write() = value; } + /// Set the value of the signal without triggering an update on subscribers. + /// + /// todo: we should make it so setting while rendering doesn't trigger an update s + pub fn set_untracked(&self, value: T) { + let mut inner = self.inner.write(); + inner.value = value; + } + /// Run a closure with a reference to the signal's value. /// If the signal has been dropped, this will panic. #[track_caller]