diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index 5ea8b209e..0e99f4ca3 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -4,9 +4,6 @@ //! This example shows how to encapsulate state in dioxus components with the reducer pattern. //! This pattern is very useful when a single component can handle many types of input that can //! be represented by an enum. -//! -//! Currently we don't have a reducer pattern hook. If you'd like to add it, -//! feel free to make a PR. use dioxus::prelude::*; @@ -15,7 +12,7 @@ fn main() { } fn app() -> Element { - let state = use_signal(PlayerState::new); + let state = use_signal(|| PlayerState { is_playing: false }); rsx!( div { @@ -38,9 +35,6 @@ struct PlayerState { } impl PlayerState { - fn new() -> Self { - Self { is_playing: false } - } fn reduce(&mut self, action: PlayerAction) { match action { PlayerAction::Pause => self.is_playing = false, diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index 8abe9ecfb..f81c05f6f 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -62,14 +62,9 @@ pub fn suspend() -> Option { None } -/// Pushes the future onto the poll queue to be polled after the component renders. -pub fn push_future(fut: impl Future + 'static) -> Option { - with_current_scope(|cx| cx.push_future(fut)) -} - /// Spawns the future but does not return the [`TaskId`] -pub fn spawn(fut: impl Future + 'static) { - with_current_scope(|cx| cx.spawn(fut)); +pub fn spawn(fut: impl Future + 'static) -> Task { + with_current_scope(|cx| cx.spawn(fut)).expect("to be in a dioxus runtime") } /// Spawn a future that Dioxus won't clean up when this component is unmounted diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 3926930e3..2567e04a4 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -87,9 +87,9 @@ pub mod prelude { pub use crate::innerlude::{ consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation, has_context, needs_update, parent_scope, provide_context, provide_root_context, - push_future, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, - suspend, try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component, - Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, + remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend, + try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element, + ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom, }; diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index 7529b49cf..d8e647fe9 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -1,7 +1,7 @@ use futures_util::task::ArcWake; use super::SchedulerMsg; -use crate::innerlude::{push_future, remove_future, Runtime}; +use crate::innerlude::{remove_future, spawn, Runtime}; use crate::ScopeId; use std::cell::RefCell; use std::future::Future; @@ -28,7 +28,7 @@ impl Task { /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which /// will only occur when the VirtualDom itself has been dropped. pub fn new(task: impl Future + 'static) -> Self { - push_future(task).expect("to be in a dioxus runtime") + spawn(task) } /// Drop the task immediately. diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 6c6526868..fc6f0e28d 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -225,18 +225,13 @@ impl ScopeContext { .expect("Runtime to exist") } - /// Pushes the future onto the poll queue to be polled after the component renders. - pub fn push_future(&self, fut: impl Future + 'static) -> Task { + /// Spawns the future but does not return the [`TaskId`] + pub fn spawn(&self, fut: impl Future + 'static) -> Task { let id = with_runtime(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist"); self.spawned_tasks.borrow_mut().insert(id); id } - /// Spawns the future but does not return the [`TaskId`] - pub fn spawn(&self, fut: impl Future + 'static) { - self.push_future(fut); - } - /// Spawn a future that Dioxus won't clean up when this component is unmounted /// /// This is good for tasks that need to be run after the component has been dropped. @@ -369,7 +364,7 @@ impl ScopeId { /// Pushes the future onto the poll queue to be polled after the component renders. pub fn push_future(self, fut: impl Future + 'static) -> Option { - with_scope(self, |cx| cx.push_future(fut)) + with_scope(self, |cx| cx.spawn(fut)) } /// Spawns the future but does not return the [`TaskId`] diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index dd3b00f8d..74b90a5e0 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -21,6 +21,7 @@ tracing = { workspace = true } thiserror = { workspace = true } slab = { workspace = true } dioxus-debug-cell = "0.1.1" +futures-util = { workspace = true} [dev-dependencies] futures-util = { workspace = true, default-features = false } diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index e6d6cadd4..ca26c82b9 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -1,7 +1,11 @@ #![allow(missing_docs)] -use dioxus_core::{ScopeState, Task}; +use dioxus_core::{ + prelude::{spawn, use_hook}, + ScopeState, Task, +}; use dioxus_signals::{use_effect, use_signal, Signal}; -use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc}; +use futures_util::{future, pin_mut, FutureExt}; +use std::{any::Any, cell::Cell, future::Future, pin::Pin, rc::Rc, sync::Arc, task::Poll}; /// A future that resolves to a value. /// @@ -21,22 +25,43 @@ where T: 'static, F: Future + 'static, { - let task = use_signal(|| None); + let value = use_signal(|| None); + let state = use_signal(|| UseFutureState::Pending); - use_effect(|| { - // task.set(); + let task = use_signal(|| { + // Create the user's task + let fut = future(); + + // Spawn a wrapper task that polls the innner future and watch its dependencies + let task = spawn(async move { + // move the future here and pin it so we can ppoll it + let mut fut = fut; + pin_mut!(fut); + + let res = future::poll_fn(|cx| { + // Set the effect stack properly + + // Poll the inner future + let ready = fut.poll_unpin(cx); + + // add any dependencies to the effect stack + + ready + }) + .await; + + // Set the value + value.set(Some(res)); + }); + + Some(task) }); - // - UseFuture { - value: todo!(), - task, - state: todo!(), - } + UseFuture { task, value, state } } pub struct UseFuture { - value: Signal, + value: Signal>, task: Signal>, state: Signal>, } diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 4aa093475..c4d201762 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -42,27 +42,6 @@ pub fn use_effect(callback: impl FnMut() + 'static) { use_hook(|| Effect::new(callback)); } -/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes. -/// The signal will be owned by the current component and will be dropped when the component is dropped. -pub fn use_effect_with_dependencies( - dependencies: D, - mut callback: impl FnMut(D::Out) + 'static, -) where - D::Out: 'static, -{ - let dependencies_signal = use_signal(|| dependencies.out()); - use_hook(|| { - Effect::new(move || { - let deref = &*dependencies_signal.read(); - callback(deref.clone()); - }); - }); - let changed = { dependencies.changed(&*dependencies_signal.read()) }; - if changed { - dependencies_signal.set(dependencies.out()); - } -} - /// Effects allow you to run code when a signal changes. Effects are run immediately and whenever any signal it reads changes. #[derive(Copy, Clone, PartialEq)] pub struct Effect {