diff --git a/Cargo.toml b/Cargo.toml index 6d78e7182..3ed970f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ dioxus-desktop = { path = "./packages/desktop", optional = true } dioxus-hooks = { path = "./packages/hooks", optional = true } dioxus-ssr = { path = "./packages/ssr", optional = true } dioxus-mobile = { path = "./packages/mobile", optional = true } +dioxus-liveview = { path = "./packages/liveview", optional = true } [features] # core @@ -26,6 +27,7 @@ macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] html = ["dioxus-html"] router = ["dioxus-router"] +liveview = ["dioxus-liveview"] # utilities atoms = [] diff --git a/examples/async.rs b/examples/async.rs index bb05669e4..7143a9af6 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -11,7 +11,7 @@ async fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |cx, _| { +pub static App: Component<()> = |cx, _| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/calculator.rs b/examples/calculator.rs index 1a918879c..d17d35b84 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -13,7 +13,7 @@ fn main() { dioxus::desktop::launch(APP, |cfg| cfg); } -const APP: FC<()> = |cx, _| { +const APP: Component<()> = |cx, _| { let cur_val = use_state(cx, || 0.0_f64); let operator = use_state(cx, || None as Option<&'static str>); let display_value = use_state(cx, || String::from("")); diff --git a/examples/coroutine.rs b/examples/coroutine.rs index c2e025e16..2fa8e25e6 100644 --- a/examples/coroutine.rs +++ b/examples/coroutine.rs @@ -27,7 +27,7 @@ fn main() { use dioxus::prelude::*; -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let p1 = use_state(cx, || 0); let p2 = use_state(cx, || 0); diff --git a/examples/crm.rs b/examples/crm.rs index 3de5b7e3a..f146e0856 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -19,7 +19,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |cx, _| { +static App: Component<()> = |cx, _| { let mut clients = use_ref(cx, || vec![] as Vec); let mut scene = use_state(cx, || Scene::ClientsList); diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs index 113f52216..74ea6a7d9 100644 --- a/examples/file_explorer.rs +++ b/examples/file_explorer.rs @@ -18,7 +18,7 @@ fn main() { }); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let file_manager = use_ref(cx, || Files::new()); let files = file_manager.read(); diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 782081c41..0330357a5 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -30,7 +30,7 @@ impl Label { } } -static App: FC<()> = |cx, _props| { +static App: Component<()> = |cx, _props| { let mut items = use_ref(cx, || vec![]); let mut selected = use_state(cx, || None); diff --git a/examples/hydration.rs b/examples/hydration.rs index f50f740ac..22a81ffe7 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -19,7 +19,7 @@ fn main() { dioxus::desktop::launch(App, |c| c.with_prerendered(content)); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut val = use_state(cx, || 0); cx.render(rsx! { diff --git a/examples/manual_edits.rs b/examples/manual_edits.rs index a24b606f9..7b4f9aaf2 100644 --- a/examples/manual_edits.rs +++ b/examples/manual_edits.rs @@ -36,7 +36,7 @@ fn main() { dioxus_desktop::run(APP, (), |c| c.with_edits(edits)); } -const APP: FC<()> = |(cx, _props)| { +const APP: Component<()> = |(cx, _props)| { rsx!(cx, div { "some app" }) diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs index d20477e81..518f60101 100644 --- a/examples/pattern_model.rs +++ b/examples/pattern_model.rs @@ -31,7 +31,7 @@ fn main() { }); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let state = use_ref(cx, || Calculator::new()); let clear_display = state.read().display_value.eq("0"); diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index 221e8eb0d..42c1976f9 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -11,7 +11,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |cx, _| { +pub static App: Component<()> = |cx, _| { let state = use_state(cx, PlayerState::new); let is_playing = state.is_playing(); diff --git a/examples/readme.rs b/examples/readme.rs index 8eb8b098f..afff68f87 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/examples/router.rs b/examples/router.rs index 5b021a807..66b86f56d 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -23,7 +23,7 @@ pub enum Route { NotFound, } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let route = use_router(cx, Route::parse)?; cx.render(rsx! { diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index c0d69535e..7a69c6de9 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -pub static Example: FC<()> = |cx, props| { +pub static Example: Component<()> = |cx, props| { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); diff --git a/examples/ssr.rs b/examples/ssr.rs index 539db8653..87bb4328e 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -9,7 +9,7 @@ fn main() { println!("{}", ssr::render_vdom(&vdom, |c| c)); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { cx.render(rsx!( div { h1 { "Title" } diff --git a/examples/tailwind.rs b/examples/tailwind.rs index fe7100762..555d13d5d 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -14,7 +14,7 @@ fn main() { const STYLE: &str = "body {overflow:hidden;}"; -pub static App: FC<()> = |cx, props| { +pub static App: Component<()> = |cx, props| { cx.render(rsx!( div { class: "overflow-hidden" style { "{STYLE}" } @@ -30,7 +30,7 @@ pub static App: FC<()> = |cx, props| { )) }; -pub static Header: FC<()> = |cx, props| { +pub static Header: Component<()> = |cx, props| { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -56,7 +56,7 @@ pub static Header: FC<()> = |cx, props| { }) }; -pub static Hero: FC<()> = |cx, props| { +pub static Hero: Component<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -94,7 +94,7 @@ pub static Hero: FC<()> = |cx, props| { } }) }; -pub static Entry: FC<()> = |cx, props| { +pub static Entry: Component<()> = |cx, props| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -107,7 +107,7 @@ pub static Entry: FC<()> = |cx, props| { }) }; -pub static StacksIcon: FC<()> = |cx, props| { +pub static StacksIcon: Component<()> = |cx, props| { cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -122,7 +122,7 @@ pub static StacksIcon: FC<()> = |cx, props| { } )) }; -pub static RightArrowIcon: FC<()> = |cx, props| { +pub static RightArrowIcon: Component<()> = |cx, props| { cx.render(rsx!( svg { fill: "none" diff --git a/examples/tasks.rs b/examples/tasks.rs index 54aa7ee5f..699ae1274 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -9,7 +9,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.push_task(async move { diff --git a/examples/todomvc.rs b/examples/todomvc.rs index cfc49f530..9d8969c33 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -22,7 +22,7 @@ pub struct TodoItem { } const STYLE: &str = include_str!("./assets/todomvc.css"); -const App: FC<()> = |cx, props| { +const App: Component<()> = |cx, props| { let mut draft = use_state(cx, || "".to_string()); let mut todos = use_state(cx, || HashMap::>::new()); let mut filter = use_state(cx, || FilterState::All); diff --git a/examples/weather_app.rs b/examples/weather_app.rs index fca6c8cf7..20fde8a28 100644 --- a/examples/weather_app.rs +++ b/examples/weather_app.rs @@ -12,7 +12,7 @@ fn main() { const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather"; -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { // let body = use_suspense( cx, @@ -40,7 +40,7 @@ static App: FC<()> = |cx, props| { #[derive(PartialEq, Props)] struct WeatherProps {} -static WeatherDisplay: FC = |cx, props| { +static WeatherDisplay: Component = |cx, props| { // cx.render(rsx!( div { class: "flex items-center justify-center flex-col" diff --git a/examples/web_tick.rs b/examples/web_tick.rs index 1e60af992..a1913e766 100644 --- a/examples/web_tick.rs +++ b/examples/web_tick.rs @@ -19,7 +19,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut rng = SmallRng::from_entropy(); let rows = (0..1_000).map(|f| { let label = Label::new(&mut rng); diff --git a/examples/webview_web.rs b/examples/webview_web.rs index debe4d8e0..55f9f4309 100644 --- a/examples/webview_web.rs +++ b/examples/webview_web.rs @@ -16,7 +16,7 @@ fn main() { dioxus::web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.render(rsx! { diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 4f09f0202..f8e53bc88 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -23,7 +23,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/handler_thru_props.rs b/packages/core/examples/handler_thru_props.rs new file mode 100644 index 000000000..368a58894 --- /dev/null +++ b/packages/core/examples/handler_thru_props.rs @@ -0,0 +1,34 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; + +fn main() { + let _ = VirtualDom::new(App); +} + +fn App(cx: Context, _props: &()) -> Element { + // + cx.render(rsx!( + div { + Child {} + } + )) +} + +struct ChildProps<'a> { + click_handler: EventHandler<'a>, +} + +fn Child(cx: Context, _props: &()) -> Element { + // + cx.render(rsx!( + div { + h1 { + "Hello, World!" + } + } + )) +} diff --git a/packages/core/examples/rows.rs b/packages/core/examples/rows.rs index 7506c418f..40ab0af7a 100644 --- a/packages/core/examples/rows.rs +++ b/packages/core/examples/rows.rs @@ -20,7 +20,7 @@ use dioxus_html as dioxus_elements; use rand::prelude::*; fn main() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); diff --git a/packages/core/examples/works.rs b/packages/core/examples/works.rs index 45104daa0..7e49da1ce 100644 --- a/packages/core/examples/works.rs +++ b/packages/core/examples/works.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; @@ -7,8 +9,8 @@ fn main() { let _ = VirtualDom::new(Parent); } -fn Parent(cx: Context, props: &()) -> Element { - let value = cx.use_hook(|_| String::new(), |f| &*f); +fn Parent(cx: Context, _props: &()) -> Element { + let value = cx.use_hook(|_| String::new(), |f| f); cx.render(rsx! { div { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f656b5a18..20f62e89f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -32,19 +32,21 @@ pub(crate) mod innerlude { pub use crate::virtual_dom::*; pub type Element = Option; - pub type FC

= for<'a> fn(Context<'a>, &'a P) -> Element; + pub type Component

= for<'a> fn(Context<'a>, &'a P) -> Element; } pub use crate::innerlude::{ - Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, - LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, - UserEvent, VElement, VFragment, VNode, VirtualDom, FC, + Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler, + EventPriority, IntoVNode, LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, + SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::innerlude::Context; - pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, Scope, FC}; + pub use crate::innerlude::{ + Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, Scope, + }; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f02b553b5..aedd5b90c 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -338,9 +338,32 @@ pub struct Listener<'bump> { pub event: &'static str, /// The actual callback that the user specified - #[allow(clippy::type_complexity)] - pub(crate) callback: - RefCell) + 'bump>>>, + pub(crate) callback: EventHandler<'bump>, +} + +pub struct EventHandler<'bump> { + pub callback: &'bump RefCell>>, +} + +impl EventHandler<'_> { + pub fn call(&self, event: Arc) { + if let Some(callback) = self.callback.borrow_mut().as_mut() { + callback(event); + } + } + pub fn release(&self) { + self.callback.replace(None); + } +} +type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc) + 'bump>; + +impl Copy for EventHandler<'_> {} +impl Clone for EventHandler<'_> { + fn clone(&self) -> Self { + Self { + callback: self.callback, + } + } } /// A cached node is a "pointer" to a "rendered" node in a particular scope @@ -608,15 +631,11 @@ impl<'a> NodeFactory<'a> { })) } - pub fn listener( - self, - event: &'static str, - callback: BumpBox<'a, dyn FnMut(Arc) + 'a>, - ) -> Listener<'a> { + pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> { Listener { event, mounted_node: Cell::new(None), - callback: RefCell::new(Some(callback)), + callback, } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index debea22de..cd7dd22b1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -274,6 +274,7 @@ impl Scope { let mut search_parent = self.parent_scope; while let Some(parent_ptr) = search_parent { + // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { return Some(shared.clone().downcast::().unwrap()); diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 1b2643d20..bf10846a8 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -281,30 +281,30 @@ impl ScopeArena { /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) { - let scope = self.get_scope(scope_id).unwrap(); + if let Some(scope) = self.get_scope(scope_id) { + let mut items = scope.items.borrow_mut(); - let mut items = scope.items.borrow_mut(); + // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we + // run the hooks (which hold an &mut Reference) + // recursively call ensure_drop_safety on all children + items.borrowed_props.drain(..).for_each(|comp| { + let scope_id = comp + .associated_scope + .get() + .expect("VComponents should be associated with a valid Scope"); - // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we - // run the hooks (which hold an &mut Reference) - // recursively call ensure_drop_safety on all children - items.borrowed_props.drain(..).for_each(|comp| { - let scope_id = comp - .associated_scope - .get() - .expect("VComponents should be associated with a valid Scope"); + self.ensure_drop_safety(&scope_id); - self.ensure_drop_safety(&scope_id); + let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); + drop_props(); + }); - let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); - drop_props(); - }); - - // Now that all the references are gone, we can safely drop our own references in our listeners. - items - .listeners - .drain(..) - .for_each(|listener| drop(listener.callback.borrow_mut().take())); + // Now that all the references are gone, we can safely drop our own references in our listeners. + items + .listeners + .drain(..) + .for_each(|listener| drop(listener.callback.callback.borrow_mut().take())); + } } pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { @@ -375,7 +375,7 @@ impl ScopeArena { // for listener in real_el.listeners.borrow().iter() { if listener.event == event.name { - let mut cb = listener.callback.borrow_mut(); + let mut cb = listener.callback.callback.borrow_mut(); if let Some(cb) = cb.as_mut() { (cb)(event.data.clone()); } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1de0e83aa..71511e4e1 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -140,7 +140,7 @@ impl VirtualDom { /// ``` /// /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. - pub fn new(root: FC<()>) -> Self { + pub fn new(root: Component<()>) -> Self { Self::new_with_props(root, ()) } @@ -174,7 +174,7 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` - pub fn new_with_props(root: FC

, root_props: P) -> Self { + pub fn new_with_props(root: Component

, root_props: P) -> Self { let (sender, receiver) = futures_channel::mpsc::unbounded::(); Self::new_with_props_and_scheduler(root, root_props, sender, receiver) } @@ -184,7 +184,7 @@ impl VirtualDom { /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the /// VirtualDom to be created just to retrieve its channel receiver. pub fn new_with_props_and_scheduler( - root: FC

, + root: Component

, root_props: P, sender: UnboundedSender, receiver: UnboundedReceiver, diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 692a8f8db..fbfcf5096 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -10,7 +10,7 @@ fn test_borrowed_state() { let _ = VirtualDom::new(Parent); } -fn Parent(cx: Context, props: &()) -> Element { +fn Parent(cx: Context, _props: &()) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f); cx.render(rsx! { diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index fce8d3f15..f6e1d57e6 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -1,4 +1,4 @@ -#![allow(unused, non_upper_case_globals)] +#![allow(unused, non_upper_case_globals, non_snake_case)] //! Prove that the dom works normally through virtualdom methods. //! @@ -13,7 +13,7 @@ use dioxus_html as dioxus_elements; mod test_logging; use DomEdit::*; -fn new_dom(app: FC

, props: P) -> VirtualDom { +fn new_dom(app: Component

, props: P) -> VirtualDom { const IS_LOGGING_ENABLED: bool = false; test_logging::set_up_logging(IS_LOGGING_ENABLED); VirtualDom::new_with_props(app, props) @@ -21,7 +21,7 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div { div { @@ -57,7 +57,7 @@ fn test_original_diff() { #[test] fn create() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div { div { @@ -120,7 +120,7 @@ fn create() { #[test] fn create_list() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" @@ -169,7 +169,7 @@ fn create_list() { #[test] fn create_simple() { - static APP: FC<()> = |cx, props| { + static APP: Component<()> = |cx, props| { cx.render(rsx! { div {} div {} @@ -207,7 +207,7 @@ fn create_simple() { } #[test] fn create_components() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx! { Child { "abc1" } Child { "abc2" } @@ -273,7 +273,7 @@ fn create_components() { } #[test] fn anchors() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 657d34f52..1ba91a1bb 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -22,7 +22,7 @@ fn manual_diffing() { value: Shared<&'static str>, } - static App: FC = |cx, props| { + static App: Component = |cx, props| { let val = props.value.lock().unwrap(); cx.render(rsx! { div { "{val}" } }) }; @@ -46,7 +46,7 @@ fn manual_diffing() { #[test] fn events_generate() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut count = use_state(cx, || 0); let inner = match *count { @@ -105,7 +105,7 @@ fn events_generate() { #[test] fn components_generate() { - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut render_phase = use_state(cx, || 0); render_phase += 1; @@ -122,7 +122,7 @@ fn components_generate() { }) }; - static Child: FC<()> = |cx, _| { + static Child: Component<()> = |cx, _| { cx.render(rsx! { h1 {} }) @@ -222,7 +222,7 @@ fn components_generate() { #[test] fn component_swap() { // simple_logger::init(); - static App: FC<()> = |cx, _| { + static App: Component<()> = |cx, _| { let mut render_phase = use_state(cx, || 0); render_phase += 1; @@ -261,7 +261,7 @@ fn component_swap() { }) }; - static NavBar: FC<()> = |cx, _| { + static NavBar: Component<()> = |cx, _| { println!("running navbar"); cx.render(rsx! { h1 { @@ -271,7 +271,7 @@ fn component_swap() { }) }; - static NavLink: FC<()> = |cx, _| { + static NavLink: Component<()> = |cx, _| { println!("running navlink"); cx.render(rsx! { h1 { @@ -280,7 +280,7 @@ fn component_swap() { }) }; - static Dashboard: FC<()> = |cx, _| { + static Dashboard: Component<()> = |cx, _| { println!("running dashboard"); cx.render(rsx! { div { @@ -289,7 +289,7 @@ fn component_swap() { }) }; - static Results: FC<()> = |cx, _| { + static Results: Component<()> = |cx, _| { println!("running results"); cx.render(rsx! { div { diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 9ec4a6a8d..4e8b84484 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -13,12 +13,12 @@ mod test_logging; fn shared_state_test() { struct MySharedState(&'static str); - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.provide_state(MySharedState("world!")); cx.render(rsx!(Child {})) }; - static Child: FC<()> = |cx, props| { + static Child: Component<()> = |cx, props| { let shared = cx.consume_state::()?; cx.render(rsx!("Hello, {shared.0}")) }; diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index dc7b76755..fcbcfbe43 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -19,7 +19,7 @@ use DomEdit::*; #[test] fn app_runs() { - static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} ); + static App: Component<()> = |cx, props| rsx!(cx, div{"hello"} ); let mut vdom = VirtualDom::new(App); let edits = vdom.rebuild(); @@ -27,7 +27,7 @@ fn app_runs() { #[test] fn fragments_work() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( div{"hello"} div{"goodbye"} @@ -41,7 +41,7 @@ fn fragments_work() { #[test] fn lists_work() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} @@ -54,7 +54,7 @@ fn lists_work() { #[test] fn conditional_rendering() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} @@ -87,13 +87,13 @@ fn conditional_rendering() { #[test] fn child_components() { - static App: FC<()> = |cx, props| { + static App: Component<()> = |cx, props| { cx.render(rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} )) }; - static Child: FC<()> = |cx, props| { + static Child: Component<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} h1 {"goodbye"} diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index be0b7847b..b86b66df4 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -15,7 +15,7 @@ fn main() { dioxus_desktop::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); log::debug!("count is {:?}", count); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 9fb86b907..df97b49a8 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -15,7 +15,6 @@ use std::{ sync::atomic::AtomicBool, sync::{Arc, RwLock}, }; -use tokio::task::LocalSet; use wry::{ application::{ accelerator::{Accelerator, SysMods}, @@ -30,14 +29,14 @@ use wry::{ }; pub fn launch( - root: FC<()>, + root: Component<()>, config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { launch_with_props(root, (), config_builder) } pub fn launch_with_props( - root: FC

, + root: Component

, props: P, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { @@ -51,7 +50,7 @@ struct Response<'a> { } pub fn run( - root: FC, + root: Component, props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { @@ -112,7 +111,7 @@ pub struct DesktopController { impl DesktopController { // Launch the virtualdom on its own thread managed by tokio // returns the desktop state - pub fn new_on_tokio(root: FC

, props: P) -> Self { + pub fn new_on_tokio(root: Component

, props: P) -> Self { let edit_queue = Arc::new(RwLock::new(VecDeque::new())); let pending_edits = edit_queue.clone(); @@ -142,7 +141,6 @@ impl DesktopController { dom.wait_for_work().await; let mut muts = dom.work_with_deadline(|| false); while let Some(edit) = muts.pop() { - log::debug!("found mutations {:?}", muts); edit_queue .write() .unwrap() diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index c09c287bb..ef5b699b6 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -9,3 +9,6 @@ pub use use_shared_state::*; mod usecoroutine; pub use usecoroutine::*; + +mod usemodel; +pub use usemodel::*; diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 12a5702e0..1a52217de 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -8,7 +8,7 @@ use std::{ type ProvidedState = RefCell>; // Tracks all the subscribers to a shared State -pub(crate) struct ProvidedStateInner { +pub struct ProvidedStateInner { value: Rc>, notify_any: Rc, consumers: HashSet, @@ -20,6 +20,14 @@ impl ProvidedStateInner { (self.notify_any)(*consumer); } } + + pub fn write(&self) -> RefMut { + self.value.borrow_mut() + } + + pub fn read(&self) -> Ref { + self.value.borrow() + } } /// This hook provides some relatively light ergonomics around shared state. @@ -114,10 +122,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> { } pub fn notify_consumers(self) { - // if !self.needs_notification.get() { - self.root.borrow_mut().notify_consumers(); - // self.needs_notification.set(true); - // } + if !self.needs_notification.get() { + self.root.borrow_mut().notify_consumers(); + self.needs_notification.set(true); + } } pub fn read_write(&self) -> (Ref<'_, T>, &Self) { @@ -138,6 +146,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> { pub fn write_silent(&self) -> RefMut<'_, T> { self.value.borrow_mut() } + + pub fn inner(&self) -> Rc>> { + self.root.clone() + } } impl Copy for UseSharedState<'_, T> {} diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs index a8a10ebc2..ecbf8b3bd 100644 --- a/packages/hooks/src/usecoroutine.rs +++ b/packages/hooks/src/usecoroutine.rs @@ -6,9 +6,9 @@ use std::{ rc::Rc, }; -pub fn use_coroutine<'a, F: Future + 'a>( +pub fn use_coroutine<'a, F: Future + 'static>( cx: Context<'a>, - f: impl FnOnce() -> F + 'a, + mut f: impl FnMut() -> F + 'a, ) -> CoroutineHandle { // cx.use_hook( @@ -62,7 +62,7 @@ pub struct CoroutineHandle<'a> { impl<'a> CoroutineHandle<'a> { pub fn start(&self) { - if self.inner.running.get() { + if self.is_running() { return; } if let Some(submit) = self.inner.submit.borrow_mut().take() { @@ -71,5 +71,10 @@ impl<'a> CoroutineHandle<'a> { self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); } } + + pub fn is_running(&self) -> bool { + self.inner.running.get() + } + pub fn resume(&self) {} } diff --git a/packages/hooks/src/usemodel.rs b/packages/hooks/src/usemodel.rs new file mode 100644 index 000000000..de991df68 --- /dev/null +++ b/packages/hooks/src/usemodel.rs @@ -0,0 +1,136 @@ +//! When building complex components, it's occasionally useful to dip into a pure MVC pattern instead of the +//! React hooks pattern. Hooks are useful to abstract over some reusable logic, but many models are not reusable +//! in the same way that hooks are. +//! +//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions. + +use dioxus_core::prelude::Context; +use futures::Future; +use std::{ + cell::{Cell, Ref, RefCell, RefMut}, + marker::PhantomData, + pin::Pin, + rc::Rc, +}; + +pub fn use_model(cx: Context, f: impl FnOnce() -> T) -> UseModel { + cx.use_hook( + |_| UseModelInner { + update_scheduled: Cell::new(false), + update_callback: cx.schedule_update(), + value: RefCell::new(f()), + // tasks: RefCell::new(Vec::new()), + }, + |inner| { + inner.update_scheduled.set(false); + UseModel { inner } + }, + ) +} + +pub struct UseModel<'a, T> { + inner: &'a UseModelInner, +} + +struct UseModelInner { + update_scheduled: Cell, + update_callback: Rc, + value: RefCell, + // tasks: RefCell>, +} + +type ModelTask = Pin + 'static>>; + +impl<'a, T: 'static> UseModel<'a, T> { + pub fn read(&self) -> Ref<'_, T> { + self.inner.value.borrow() + } + pub fn write(&self) -> RefMut<'_, T> { + self.needs_update(); + self.inner.value.borrow_mut() + } + /// Allows the ability to write the value without forcing a re-render + pub fn write_silent(&self) -> RefMut<'_, T> { + self.inner.value.borrow_mut() + } + + pub fn needs_update(&self) { + if !self.inner.update_scheduled.get() { + self.inner.update_scheduled.set(true); + (self.inner.update_callback)(); + } + } + + pub fn set(&self, new: T) { + *self.inner.value.borrow_mut() = new; + self.needs_update(); + } + + pub fn read_write(&self) -> (Ref<'_, T>, &Self) { + (self.read(), self) + } + + pub fn start(&self, f: impl FnOnce() -> ModelTask) { + todo!() + } +} + +// keep a coroutine going +pub fn use_model_coroutine + 'static>( + cx: Context, + model: UseModel, + f: impl FnOnce(AppModels) -> F, +) -> UseModelCoroutine { + cx.use_hook( + |_| { + // + UseModelTaskInner { + task: Default::default(), + } + }, + |inner| { + if let Some(task) = inner.task.get_mut() { + cx.push_task(|| task); + } + // + todo!() + }, + ) +} + +pub struct UseModelCoroutine {} + +struct UseModelTaskInner { + task: RefCell>, +} + +impl UseModelCoroutine { + pub fn start(&self) {} +} + +pub struct ModelAsync { + _p: PhantomData, +} +impl ModelAsync { + pub fn write(&self) -> RefMut<'_, T> { + todo!() + } + pub fn read(&self) -> Ref<'_, T> { + todo!() + } +} + +pub struct AppModels {} + +impl AppModels { + pub fn get(&self) -> ModelAsync { + unimplemented!() + } +} + +impl Copy for UseModel<'_, T> {} +impl<'a, T> Clone for UseModel<'a, T> { + fn clone(&self) -> Self { + Self { inner: self.inner } + } +} diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 9ebfbfebf..c1362f4da 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -8,12 +8,12 @@ use dioxus_core::Context; pub fn use_ref(cx: Context, f: impl FnOnce() -> T) -> UseRef { cx.use_hook( |_| UseRefInner { - update_scheuled: Cell::new(false), + update_scheduled: Cell::new(false), update_callback: cx.schedule_update(), value: RefCell::new(f()), }, |inner| { - inner.update_scheuled.set(false); + inner.update_scheduled.set(false); UseRef { inner } }, ) @@ -23,7 +23,7 @@ pub struct UseRef<'a, T> { inner: &'a UseRefInner, } struct UseRefInner { - update_scheuled: Cell, + update_scheduled: Cell, update_callback: Rc, value: RefCell, } @@ -54,8 +54,8 @@ impl<'a, T> UseRef<'a, T> { } pub fn needs_update(&self) { - if !self.inner.update_scheuled.get() { - self.inner.update_scheuled.set(true); + if !self.inner.update_scheduled.get() { + self.inner.update_scheduled.set(true); (self.inner.update_callback)(); } } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index d215c87a7..853225df4 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -8,7 +8,7 @@ use std::{ /// Store state between component renders! /// -/// ## The "Pinnacle" of state hooks +/// ## Dioxus equivalent of useState, designed for Rust /// /// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and /// modify state between component renders. When the state is updated, the component will re-render. @@ -54,17 +54,15 @@ pub fn use_state<'a, T: 'static>( initial_state_fn: impl FnOnce() -> T, ) -> UseState<'a, T> { cx.use_hook( - move |_| { - // - UseStateInner { - current_val: initial_state_fn(), - update_callback: cx.schedule_update(), - wip: Rc::new(RefCell::new(None)), - update_scheuled: Cell::new(false), - } + move |_| UseStateInner { + current_val: initial_state_fn(), + update_callback: cx.schedule_update(), + wip: Rc::new(RefCell::new(None)), + update_scheuled: Cell::new(false), }, move |hook| { hook.update_scheuled.set(false); + let mut new_val = hook.wip.borrow_mut(); if new_val.is_some() { hook.current_val = new_val.take().unwrap(); @@ -134,13 +132,13 @@ impl<'a, T: 'static> UseState<'a, T> { pub fn setter(&self) -> Rc { let slot = self.inner.wip.clone(); Rc::new(move |new| { - // *slot.borrow_mut() = Some(new); }) } pub fn for_async(&self) -> AsyncUseState { AsyncUseState { + re_render: self.inner.update_callback.clone(), wip: self.inner.wip.clone(), } } @@ -169,6 +167,10 @@ impl<'a, T: 'static + ToOwned> UseState<'a, T> { slot.as_mut().unwrap() }) } + + pub fn inner(self) -> T { + self.inner.current_val.to_owned() + } } impl<'a, T> std::ops::Deref for UseState<'a, T> { @@ -254,6 +256,7 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { /// A less ergonmic but still capable form of use_state that's valid for `static lifetime pub struct AsyncUseState { + re_render: Rc, wip: Rc>>, } @@ -269,7 +272,10 @@ impl AsyncUseState { slot.as_mut().unwrap() }) } +} +impl AsyncUseState { pub fn set(&mut self, val: T) { + (self.re_render)(); *self.wip.borrow_mut() = Some(val); } } diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index 0ede805f7..9292f8e64 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -44,7 +44,11 @@ pub mod on { // ie copy let shortname: &'static str = &event_name[2..]; - c.listener(shortname, callback) + let handler = EventHandler { + callback: bump.alloc(std::cell::RefCell::new(Some(callback))), + }; + + c.listener(shortname, handler) } )* )* diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml new file mode 100644 index 000000000..1fb2905fc --- /dev/null +++ b/packages/liveview/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dioxus-liveview" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.4.2", optional = true, features = ["ws", "headers"] } + +[features] +default = ["axum"] + +[dev-dependencies] +tokio = { version = "1.14.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tower-http = { version = "0.2.0", features = ["fs", "trace"] } +headers = "0.3" diff --git a/packages/liveview/README.md b/packages/liveview/README.md new file mode 100644 index 000000000..e06a662bc --- /dev/null +++ b/packages/liveview/README.md @@ -0,0 +1,24 @@ +# Dioxus LiveView + +Enabling server-rendered and hybrid applications with incredibly low latency (<1ms). + +```rust +#[async_std::main] +async fn main() -> tide::Result<()> { + let liveview_pool = dioxus::liveview::pool::default(); + let mut app = tide::new(); + + // serve the liveview client + app.at("/").get(dioxus::liveview::liveview_frontend); + + // and then connect the client to the backend + app.at("/app").get(|req| dioxus::liveview::launch(App, Props { req })) + + app.listen("127.0.0.1:8080").await?; + + Ok(()) +} +``` + +Dioxus LiveView runs your Dioxus apps on the server + diff --git a/packages/liveview/examples/axum.rs b/packages/liveview/examples/axum.rs new file mode 100644 index 000000000..f280a25c6 --- /dev/null +++ b/packages/liveview/examples/axum.rs @@ -0,0 +1,96 @@ +//! Example websocket server. +//! +//! Run with +//! +//! ```not_rust +//! cargo run -p example-websockets +//! ``` + +use axum::{ + extract::{ + ws::{Message, WebSocket, WebSocketUpgrade}, + TypedHeader, + }, + http::StatusCode, + response::IntoResponse, + routing::{get, get_service}, + Router, +}; +use std::net::SocketAddr; +use tower_http::{ + services::ServeDir, + trace::{DefaultMakeSpan, TraceLayer}, +}; + +#[tokio::main] +async fn main() { + // Set the RUST_LOG, if it hasn't been explicitly defined + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_websockets=debug,tower_http=debug") + } + tracing_subscriber::fmt::init(); + + // build our application with some routes + let app = Router::new() + .fallback( + get_service( + ServeDir::new("examples/axum_assets").append_index_html_on_directories(true), + ) + .handle_error(|error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }), + ) + // routes are matched from bottom to top, so we have to put `nest` at the + // top since it matches all routes + .route("/ws", get(ws_handler)) + // logging so we can see whats going on + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::default().include_headers(true)), + ); + + // run it with hyper + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn ws_handler( + ws: WebSocketUpgrade, + user_agent: Option>, +) -> impl IntoResponse { + if let Some(TypedHeader(user_agent)) = user_agent { + println!("`{}` connected", user_agent.as_str()); + } + + ws.on_upgrade(handle_socket) +} + +async fn handle_socket(mut socket: WebSocket) { + if let Some(msg) = socket.recv().await { + if let Ok(msg) = msg { + println!("Client says: {:?}", msg); + } else { + println!("client disconnected"); + return; + } + } + + loop { + if socket + .send(Message::Text(String::from("Hi!"))) + .await + .is_err() + { + println!("client disconnected"); + return; + } + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } +} diff --git a/packages/liveview/examples/axum_assets/index.html b/packages/liveview/examples/axum_assets/index.html new file mode 100644 index 000000000..390bb86b7 --- /dev/null +++ b/packages/liveview/examples/axum_assets/index.html @@ -0,0 +1 @@ + diff --git a/packages/liveview/examples/axum_assets/script.js b/packages/liveview/examples/axum_assets/script.js new file mode 100644 index 000000000..264efe809 --- /dev/null +++ b/packages/liveview/examples/axum_assets/script.js @@ -0,0 +1,9 @@ +const socket = new WebSocket('ws://localhost:3000/ws'); + +socket.addEventListener('open', function (event) { + socket.send('Hello Server!'); +}); + +socket.addEventListener('message', function (event) { + console.log('Message from server ', event.data); +}); diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/actix_handler.rs b/packages/liveview/src/supported/actix_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/axum_handler.rs b/packages/liveview/src/supported/axum_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/liveview/src/supported/tide_handler.rs b/packages/liveview/src/supported/tide_handler.rs new file mode 100644 index 000000000..e69de29bb diff --git a/packages/mobile/src/lib.rs b/packages/mobile/src/lib.rs index a3452c8b4..6d7f27114 100644 --- a/packages/mobile/src/lib.rs +++ b/packages/mobile/src/lib.rs @@ -26,11 +26,14 @@ fn init_logging() { static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html"); -pub fn launch(root: FC<()>, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> { +pub fn launch( + root: Component<()>, + builder: fn(WindowBuilder) -> WindowBuilder, +) -> anyhow::Result<()> { launch_with_props(root, (), builder) } pub fn launch_with_props( - root: FC

, + root: Component

, props: P, builder: fn(WindowBuilder) -> WindowBuilder, ) -> anyhow::Result<()> { @@ -41,7 +44,7 @@ pub fn launch_with_props( /// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease. pub struct WebviewRenderer { /// The root component used to render the Webview - root: FC, + root: Component, } enum RpcEvent<'a> { Initialize { @@ -52,7 +55,7 @@ enum RpcEvent<'a> { impl WebviewRenderer { pub fn run( - root: FC, + root: Component, props: T, user_builder: fn(WindowBuilder) -> WindowBuilder, ) -> anyhow::Result<()> { @@ -60,7 +63,7 @@ impl WebviewRenderer { } pub fn run_with_edits( - root: FC, + root: Component, props: T, user_builder: fn(WindowBuilder) -> WindowBuilder, redits: Option>>, diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 4a317dd5c..6c604b88a 100644 --- a/packages/router/examples/simple.rs +++ b/packages/router/examples/simple.rs @@ -9,7 +9,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { #[derive(Clone, Debug, PartialEq)] enum Route { Home, diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 84215517c..e092daca7 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -301,13 +301,13 @@ mod tests { use dioxus_core_macro::*; use dioxus_html as dioxus_elements; - static SIMPLE_APP: FC<()> = |cx, _| { + static SIMPLE_APP: Component<()> = |cx, _| { cx.render(rsx!(div { "hello world!" })) }; - static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, _| { + static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| { cx.render(rsx! { div { title: "About W3Schools" @@ -326,14 +326,14 @@ mod tests { }) }; - static NESTED_APP: FC<()> = |cx, _| { + static NESTED_APP: Component<()> = |cx, _| { cx.render(rsx!( div { SIMPLE_APP {} } )) }; - static FRAGMENT_APP: FC<()> = |cx, _| { + static FRAGMENT_APP: Component<()> = |cx, _| { cx.render(rsx!( div { "f1" } div { "f2" } @@ -389,7 +389,7 @@ mod tests { #[test] fn styles() { - static STLYE_APP: FC<()> = |cx, _| { + static STLYE_APP: Component<()> = |cx, _| { cx.render(rsx! { div { color: "blue", font_size: "46px" } }) diff --git a/packages/web/examples/async.rs b/packages/web/examples/async.rs index 09301ba27..c6cedf9be 100644 --- a/packages/web/examples/async.rs +++ b/packages/web/examples/async.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let mut count = use_state(cx, || 0); cx.push_task(|| async move { diff --git a/packages/web/examples/js_bench.rs b/packages/web/examples/js_bench.rs index 9ce90d841..eeb7dc622 100644 --- a/packages/web/examples/js_bench.rs +++ b/packages/web/examples/js_bench.rs @@ -107,7 +107,7 @@ impl Label { } } -static App: FC<()> = |cx, _props| { +static App: Component<()> = |cx, _props| { let mut items = use_ref(cx, || vec![]); let mut selected = use_state(cx, || None); diff --git a/packages/web/examples/simple.rs b/packages/web/examples/simple.rs index f6a397e00..d93591952 100644 --- a/packages/web/examples/simple.rs +++ b/packages/web/examples/simple.rs @@ -15,7 +15,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, props| { +static App: Component<()> = |cx, props| { let show = use_state(cx, || true); let inner = match *show { diff --git a/packages/web/examples/suspense.rs b/packages/web/examples/suspense.rs index e0c7b690c..615351a81 100644 --- a/packages/web/examples/suspense.rs +++ b/packages/web/examples/suspense.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(App, |c| c); } -static App: FC<()> = |cx, _| { +static App: Component<()> = |cx, _| { let doggo = cx.suspend(|| async move { #[derive(serde::Deserialize)] struct Doggo { diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index d9e14b6c2..ba33f5a60 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -60,7 +60,7 @@ use cache::intern_cached_strings; use dioxus::SchedulerMsg; use dioxus::VirtualDom; pub use dioxus_core as dioxus; -use dioxus_core::prelude::FC; +use dioxus_core::prelude::Component; use futures_util::FutureExt; mod cache; @@ -89,7 +89,7 @@ mod ric_raf; /// rsx!(cx, div {"hello world"}) /// } /// ``` -pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) { +pub fn launch(root_component: Component<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) { launch_with_props(root_component, (), configuration) } @@ -113,8 +113,11 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W /// rsx!(cx, div {"hello {props.name}"}) /// } /// ``` -pub fn launch_with_props(root_component: FC, root_properties: T, configuration_builder: F) -where +pub fn launch_with_props( + root_component: Component, + root_properties: T, + configuration_builder: F, +) where T: Send + 'static, F: FnOnce(WebConfig) -> WebConfig, { @@ -134,7 +137,7 @@ where /// wasm_bindgen_futures::spawn_local(app_fut); /// } /// ``` -pub async fn run_with_props(root: FC, root_props: T, cfg: WebConfig) { +pub async fn run_with_props(root: Component, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); intern_cached_strings();