Change context api to panic with nice error message

This commit is contained in:
Jonathan Kelley committed 2024-01-15 18:02:58 -08:00
1 parent ee5020c41f
commit 14651a3573
19 files changed
+96 -69

No files matched your search

+7 -5
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
use dioxus::{core::CapturedError, prelude::*};
use dioxus::{dioxus_core::CapturedError, prelude::*};
fn main() {
dioxus_desktop::launch(app);
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
};
-1
View File
@@ -1,5 +1,4 @@
use crate::ScopeId;
use slab::Slab;
mod task;
mod wait;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
}