Explorar o código

Change context api to panic with nice error message

Jonathan Kelley hai 1 ano
pai
achega
14651a3573

+ 7 - 5
examples/compose.rs

@@ -1,8 +1,9 @@
 //! This example shows how to create a popup window and send data back to the parent window.
 
+use std::rc::Rc;
+
 use dioxus::prelude::*;
 use futures_util::StreamExt;
-use tokio::sync::mpsc::UnboundedSender;
 
 fn main() {
     dioxus_desktop::launch(app);
@@ -12,15 +13,16 @@ fn app() -> Element {
     let emails_sent = use_signal(|| Vec::new() as Vec<String>);
 
     // Wait for responses to the compose channel, and then push them to the emails_sent signal.
-    let tx = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
+    let handle = use_coroutine(|mut rx: UnboundedReceiver<String>| async move {
         while let Some(message) = rx.next().await {
             emails_sent.write().push(message);
         }
     });
 
     let open_compose_window = move |evt: MouseEvent| {
+        let tx = handle.tx();
         dioxus_desktop::window().new_window(
-            VirtualDom::new_with_props(compose, tx.clone()),
+            VirtualDom::new_with_props(compose, Rc::new(move |s| tx.unbounded_send(s))),
             Default::default(),
         );
     };
@@ -39,7 +41,7 @@ fn app() -> Element {
     }
 }
 
-fn compose(tx: UnboundedSender<String>) -> Element {
+fn compose(send: Rc<dyn Fn(String)>) -> Element {
     let user_input = use_signal(String::new);
 
     rsx! {
@@ -48,7 +50,7 @@ fn compose(tx: UnboundedSender<String>) -> Element {
 
             button {
                 onclick: move |_| {
-                    tx.send(user_input.get().clone());
+                    send(user_input.get().clone());
                     dioxus_desktop::window().close();
                 },
                 "Click to send"

+ 40 - 35
examples/crm.rs

@@ -3,29 +3,17 @@ use dioxus::prelude::*;
 use dioxus_router::prelude::*;
 
 fn main() {
-    dioxus_desktop::launch(App);
+    dioxus_desktop::launch(app);
 }
 
-#[derive(Routable, Clone)]
-#[rustfmt::skip]
-enum Route {
-    #[route("/")]
-    ClientList {},
-    #[route("/new")]
-    ClientAdd {},
-    #[route("/settings")]
-    Settings {},
-}
+/// A type alias that reprsents a shared context between components
+///
+/// Normally we'd wrap the Context in a newtype, but we only have one Signal<Vec<Client>> in this app
+type Clients = Signal<Vec<Client>>;
 
-#[derive(Clone, Debug, Default)]
-pub struct Client {
-    pub first_name: String,
-    pub last_name: String,
-    pub description: String,
-}
+fn app() -> Element {
+    use_context_provider::<Clients>(|| Signal::new(vec![]));
 
-#[component]
-fn App() -> Element {
     render! {
         link {
             rel: "stylesheet",
@@ -48,8 +36,30 @@ fn App() -> Element {
     }
 }
 
+#[derive(Routable, Clone)]
+#[rustfmt::skip]
+enum Route {
+    #[route("/")]
+    ClientList {},
+
+    #[route("/new")]
+    ClientAdd {},
+
+    #[route("/settings")]
+    Settings {},
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Client {
+    pub first_name: String,
+    pub last_name: String,
+    pub description: String,
+}
+
 #[component]
 fn ClientList() -> Element {
+    let clients = use_context::<Clients>();
+
     rsx! {
         h2 { "List of Clients" }
         Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
@@ -69,21 +79,18 @@ fn ClientAdd() -> Element {
     let last_name = use_signal(String::new);
     let description = use_signal(String::new);
 
+    let submit_client = move |_: FormEvent| {
+        consume_context::<Clients>().write().push(Client {
+            first_name: first_name.to_string(),
+            last_name: last_name.to_string(),
+            description: description.to_string(),
+        });
+        dioxus_router::router().push(Route::ClientList {});
+    };
+
     rsx! {
         h2 { "Add new Client" }
-
-        form {
-            class: "pure-form pure-form-aligned",
-            onsubmit: move |_| {
-                let mut clients = clients.write();
-                clients
-                    .push(Client {
-                        first_name: first_name.to_string(),
-                        last_name: last_name.to_string(),
-                        description: description.to_string(),
-                    });
-                dioxus_router::router().push(Route::ClientList {});
-            },
+        form { class: "pure-form pure-form-aligned", onsubmit: submit_client,
 
             fieldset {
                 div { class: "pure-control-group",
@@ -131,14 +138,12 @@ fn ClientAdd() -> Element {
 
 #[component]
 fn Settings() -> Element {
-    let clients = use_shared_state::<ClientContext>().unwrap();
-
     rsx! {
         h2 { "Settings" }
 
         button {
             class: "pure-button pure-button-primary red",
-            onclick: move |_| clients.write().clear(),
+            onclick: move |_| consume_context::<Clients>().write().clear(),
             "Remove all Clients"
         }
 

+ 1 - 1
examples/error_handle.rs

@@ -1,4 +1,4 @@
-use dioxus::{core::CapturedError, prelude::*};
+use dioxus::{dioxus_core::CapturedError, prelude::*};
 
 fn main() {
     dioxus_desktop::launch(app);

+ 1 - 1
examples/spread.rs

@@ -1,4 +1,4 @@
-use dioxus::{core::NoOpMutations, prelude::*};
+use dioxus::{dioxus_core::NoOpMutations, prelude::*};
 
 fn main() {
     let mut dom = VirtualDom::new(app);

+ 2 - 2
packages/core/src/error_boundary.rs

@@ -1,5 +1,5 @@
 use crate::{
-    global_context::{consume_context, current_scope_id},
+    global_context::{current_scope_id, try_consume_context},
     innerlude::provide_context,
     use_hook, Element, IntoDynNode, Properties, ScopeId, Template, TemplateAttribute, TemplateNode,
     VNode,
@@ -202,7 +202,7 @@ pub trait Throw<S = ()>: Sized {
 }
 
 fn throw_error<T>(e: impl Debug + 'static) -> Option<T> {
-    if let Some(cx) = consume_context::<ErrorBoundary>() {
+    if let Some(cx) = try_consume_context::<ErrorBoundary>() {
         match current_scope_id() {
             Some(id) => cx.insert_error(id, Box::new(e), Backtrace::capture()),
             None => {

+ 8 - 1
packages/core/src/global_context.rs

@@ -19,10 +19,17 @@ pub fn vdom_is_rendering() -> bool {
 }
 
 /// Consume context from the current scope
-pub fn consume_context<T: 'static + Clone>() -> Option<T> {
+pub fn try_consume_context<T: 'static + Clone>() -> Option<T> {
     with_current_scope(|cx| cx.consume_context::<T>()).flatten()
 }
 
+/// Consume context from the current scope
+pub fn consume_context<T: 'static + Clone>() -> T {
+    with_current_scope(|cx| cx.consume_context::<T>())
+        .flatten()
+        .unwrap_or_else(|| panic!("Could not find context {}", std::any::type_name::<T>(),))
+}
+
 /// Consume context from the current scope
 pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
     with_runtime(|rt| {

+ 2 - 2
packages/core/src/lib.rs

@@ -88,8 +88,8 @@ pub mod prelude {
         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, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element,
-        ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
+        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,
     };

+ 0 - 1
packages/core/src/scheduler/mod.rs

@@ -1,5 +1,4 @@
 use crate::ScopeId;
-use slab::Slab;
 
 mod task;
 mod wait;

+ 1 - 1
packages/desktop/src/desktop_context.rs

@@ -33,7 +33,7 @@ use tao::platform::ios::WindowExtIOS;
 ///
 /// This function will panic if it is called outside of the context of a Dioxus App.
 pub fn window() -> DesktopContext {
-    dioxus_core::prelude::consume_context().unwrap()
+    dioxus_core::prelude::consume_context()
 }
 
 /// A handle to the [`DesktopService`] that can be passed around.

+ 1 - 1
packages/desktop/src/hooks.rs

@@ -14,7 +14,7 @@ use wry::RequestAsyncResponder;
 
 /// Get an imperative handle to the current window
 pub fn use_window() -> DesktopContext {
-    use_hook(|| consume_context::<DesktopContext>()).unwrap()
+    use_hook(|| consume_context::<DesktopContext>())
 }
 
 /// Get a closure that executes any JavaScript in the WebView context.

+ 2 - 2
packages/hooks/src/lib.rs

@@ -58,8 +58,8 @@ macro_rules! to_owned {
 mod use_on_destroy;
 pub use use_on_destroy::*;
 
-// mod use_context;
-// pub use use_context::*;
+mod use_context;
+pub use use_context::*;
 
 mod use_coroutine;
 pub use use_coroutine::*;

+ 17 - 6
packages/hooks/src/use_context.rs

@@ -1,20 +1,31 @@
-use dioxus_core::ScopeState;
+use dioxus_core::{
+    prelude::{consume_context, provide_context, try_consume_context},
+    use_hook,
+};
 
 /// Consume some context in the tree, providing a sharable handle to the value
 ///
 /// Does not regenerate the value if the value is changed at the parent.
 #[must_use]
-pub fn use_context<T: 'static + Clone>() -> Option<&T> {
-    cx.use_hook(|| cx.consume_context::<T>()).as_ref()
+pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
+    use_hook(|| try_consume_context::<T>())
+}
+
+/// Consume some context in the tree, providing a sharable handle to the value
+///
+/// Does not regenerate the value if the value is changed at the parent.
+#[must_use]
+pub fn use_context<T: 'static + Clone>() -> T {
+    use_hook(|| consume_context::<T>())
 }
 
 /// Provide some context via the tree and return a reference to it
 ///
 /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.
-pub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> &T {
-    cx.use_hook(|| {
+pub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> T {
+    use_hook(|| {
         let val = f();
-        cx.provide_context(val.clone());
+        provide_context(val.clone());
         val
     })
 }

+ 5 - 1
packages/hooks/src/use_coroutine.rs

@@ -96,7 +96,7 @@ where
 ///
 /// See the docs for [`use_coroutine`] for more details.
 #[must_use]
-pub fn use_coroutine_handle<M: 'static>() -> Option<Coroutine<M>> {
+pub fn use_coroutine_handle<M: 'static>() -> Coroutine<M> {
     use_hook(|| consume_context::<Coroutine<M>>())
 }
 
@@ -118,6 +118,10 @@ impl<T> Coroutine<T> {
         let _ = self.tx.read().as_ref().unwrap().unbounded_send(msg);
     }
 
+    pub fn tx(&self) -> UnboundedSender<T> {
+        self.tx.read().as_ref().unwrap().clone()
+    }
+
     /// Restart this coroutine
     ///
     /// Forces the component to re-render, which will re-invoke the coroutine.

+ 2 - 2
packages/hooks/src/use_root_context.rs

@@ -1,9 +1,9 @@
-use dioxus_core::{prelude::consume_context, prelude::provide_root_context, use_hook};
+use dioxus_core::{prelude::provide_root_context, prelude::try_consume_context, use_hook};
 
 ///
 pub fn use_root_context<T: 'static + Clone>(new: impl FnOnce() -> T) -> T {
     use_hook(|| {
-        consume_context::<T>()
+        try_consume_context::<T>()
             // If no context is provided, create a new one at the root
             .unwrap_or_else(|| provide_root_context(new()).expect(" A runtime to exist"))
     })

+ 2 - 3
packages/html/src/eval.rs

@@ -35,7 +35,7 @@ type EvalCreator = Rc<dyn Fn(&str) -> Result<UseEval, EvalError>>;
 /// has access to most, if not all of your application data.**
 #[must_use]
 pub fn eval_provider() -> EvalCreator {
-    let eval_provider = consume_context::<Rc<dyn EvalProvider>>().expect("evaluator not provided");
+    let eval_provider = consume_context::<Rc<dyn EvalProvider>>();
 
     Rc::new(move |script: &str| {
         eval_provider
@@ -45,8 +45,7 @@ pub fn eval_provider() -> EvalCreator {
 }
 
 pub fn eval(script: &str) -> Result<UseEval, EvalError> {
-    let eval_provider = dioxus_core::prelude::consume_context::<Rc<dyn EvalProvider>>()
-        .expect("evaluator not provided");
+    let eval_provider = dioxus_core::prelude::consume_context::<Rc<dyn EvalProvider>>();
 
     eval_provider
         .new_evaluator(script.to_string())

+ 1 - 1
packages/router/src/contexts/navigator.rs

@@ -9,7 +9,7 @@ use crate::prelude::{ExternalNavigationFailure, IntoRoutable, RouterContext};
 /// Panics if there is no router present.
 pub fn navigator() -> Navigator {
     Navigator(
-        dioxus_lib::prelude::consume_context::<RouterContext>()
+        dioxus_lib::prelude::try_consume_context::<RouterContext>()
             .expect("A router must be present to use navigator"),
     )
 }

+ 1 - 1
packages/router/src/contexts/outlet.rs

@@ -18,7 +18,7 @@ impl<R> Clone for OutletContext<R> {
 
 pub(crate) fn use_outlet_context<R: 'static>() -> OutletContext<R> {
     use_hook(|| {
-        consume_context().unwrap_or(OutletContext::<R> {
+        try_consume_context().unwrap_or(OutletContext::<R> {
             current_level: 1,
             _marker: std::marker::PhantomData,
         })

+ 2 - 2
packages/router/src/hooks/use_navigator.rs

@@ -1,4 +1,4 @@
-use dioxus_lib::prelude::{consume_context, use_hook};
+use dioxus_lib::prelude::{consume_context, try_consume_context, use_hook};
 
 use crate::prelude::{Navigator, RouterContext};
 
@@ -51,7 +51,7 @@ use crate::prelude::{Navigator, RouterContext};
 #[must_use]
 pub fn use_navigator() -> Navigator {
     use_hook(|| {
-        let router = consume_context::<RouterContext>()
+        let router = try_consume_context::<RouterContext>()
             .expect("Must be called in a descendant of a Router component");
 
         Navigator(router)

+ 1 - 1
packages/router/src/hooks/use_router.rs

@@ -9,5 +9,5 @@ pub fn use_router() -> RouterContext {
 
 /// Aquire the router without subscribing to updates.
 pub fn router() -> RouterContext {
-    dioxus_lib::prelude::consume_context().unwrap()
+    dioxus_lib::prelude::consume_context()
 }