From f2caa3a3ad4f007a196885d23e805d75467a05a5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 15 Jan 2024 11:54:17 -0800 Subject: [PATCH] clean up some more examples --- examples/ssr.rs | 6 +- examples/tasks.rs | 2 +- examples/todomvc.rs | 198 ++++++++++++++++++-------------------------- 3 files changed, 84 insertions(+), 122 deletions(-) diff --git a/examples/ssr.rs b/examples/ssr.rs index 333f2b51b..f8980a5f3 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -2,12 +2,12 @@ //! //! This example shows how we can render the Dioxus Virtualdom using SSR. -use dioxus::prelude::*; +use dioxus::{core::NoOpMutations, prelude::*}; fn main() { // We can render VirtualDoms let mut vdom = VirtualDom::new(app); - let _ = vdom.rebuild(); + let _ = vdom.rebuild(&mut NoOpMutations); println!("{}", dioxus_ssr::render(&vdom)); // Or we can render rsx! calls themselves @@ -36,5 +36,5 @@ fn app() -> Element { h1 { "Title" } p { "Body" } } - )) + ) } diff --git a/examples/tasks.rs b/examples/tasks.rs index 0eb60a10a..8834a28df 100644 --- a/examples/tasks.rs +++ b/examples/tasks.rs @@ -12,7 +12,7 @@ fn main() { fn app() -> Element { let mut count = use_signal(|| 0); - use_future(move |_| async move { + use_future(move || async move { loop { tokio::time::sleep(Duration::from_millis(1000)).await; count += 1; diff --git a/examples/todomvc.rs b/examples/todomvc.rs index 4bb6fe05d..b45fef870 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] - use dioxus::prelude::*; use dioxus_elements::input_data::keyboard_types::Key; +use std::collections::HashMap; fn main() { dioxus_desktop::launch(app); @@ -21,67 +21,56 @@ pub struct TodoItem { pub contents: String, } -pub fn app(cx: Scope<()>) -> Element { - let todos = use_signal(im_rc::HashMap::::default); +pub fn app(_props: ()) -> Element { + let todos = use_signal(|| HashMap::::new()); let filter = use_signal(|| FilterState::All); - // Filter the todos based on the filter state - let mut filtered_todos = todos - .iter() - .filter(|(_, item)| match **filter { - FilterState::All => true, - FilterState::Active => !item.checked, - FilterState::Completed => item.checked, - }) - .map(|f| *f.0) - .collect::>(); - filtered_todos.sort_unstable(); + let active_todo_count = + use_selector(move || todos().values().filter(|item| !item.checked).count()); - let active_todo_count = todos.values().filter(|item| !item.checked).count(); - let active_todo_text = match active_todo_count { - 1 => "item", - _ => "items", - }; + let filtered_todos = use_selector(move || { + let mut filtered_todos = todos() + .iter() + .filter(|(_, item)| match *filter() { + FilterState::All => true, + FilterState::Active => !item.checked, + FilterState::Completed => item.checked, + }) + .map(|f| *f.0) + .collect::>(); - let show_clear_completed = todos.values().any(|todo| todo.checked); + filtered_todos.sort_unstable(); + + filtered_todos + }); rsx! { section { class: "todoapp", style { {include_str!("./assets/todomvc.css")} } - TodoHeader { todos: todos } + TodoHeader { todos } section { class: "main", - if !todos.is_empty() { + if !todos().is_empty() { input { id: "toggle-all", class: "toggle-all", r#type: "checkbox", onchange: move |_| { - let check = active_todo_count != 0; - for (_, item) in todos.make_mut().iter_mut() { + let check = *active_todo_count() != 0; + for (_, item) in todos.write().iter_mut() { item.checked = check; } }, - checked: if active_todo_count == 0 { "true" } else { "false" }, + checked: *active_todo_count() == 0, } label { r#for: "toggle-all" } } ul { class: "todo-list", - for id in filtered_todos.iter() { - TodoEntry { - key: "{id}", - id: *id, - todos: todos, - } + for id in filtered_todos().iter().copied() { + TodoEntry { key: "{id}", id, todos } } } - if !todos.is_empty() { - ListFooter { - active_todo_count: active_todo_count, - active_todo_text: active_todo_text, - show_clear_completed: show_clear_completed, - todos: todos, - filter: filter, - } + if !todos().is_empty() { + ListFooter { active_todo_count, todos, filter } } } } @@ -89,14 +78,24 @@ pub fn app(cx: Scope<()>) -> Element { } } -#[derive(Props)] -pub struct TodoHeaderProps<'a> { - todos: &'a UseState>, -} - -pub fn TodoHeader(props: TodoHeaderProps) -> Element { +#[component] +pub fn TodoHeader(todos: Signal>) -> Element { let draft = use_signal(|| "".to_string()); - let todo_id = use_signal(|| 0); + let mut todo_id = use_signal(|| 0); + + let onkeydown = move |evt: KeyboardEvent| { + if evt.key() == Key::Enter && !draft().is_empty() { + let id = *todo_id(); + let todo = TodoItem { + id, + checked: false, + contents: draft.to_string(), + }; + todos.write().insert(id, todo); + todo_id += 1; + draft.set("".to_string()); + } + }; rsx! { header { class: "header", @@ -106,76 +105,46 @@ pub fn TodoHeader(props: TodoHeaderProps) -> Element { placeholder: "What needs to be done?", value: "{draft}", autofocus: "true", - oninput: move |evt| { - draft.set(evt.value().clone()); - }, - onkeydown: move |evt| { - if evt.key() == Key::Enter && !draft.is_empty() { - cx.props - .todos - .make_mut() - .insert( - **todo_id, - TodoItem { - id: **todo_id, - checked: false, - contents: draft.to_string(), - }, - ); - *todo_id.make_mut() += 1; - draft.set("".to_string()); - } - } + oninput: move |evt| draft.set(evt.value().clone()), + onkeydown, } } } } -#[derive(Props)] -pub struct TodoEntryProps<'a> { - todos: &'a UseState>, - id: u32, -} - -pub fn TodoEntry(props: TodoEntryProps) -> Element { +#[component] +pub fn TodoEntry(todos: Signal>, id: u32) -> Element { let is_editing = use_signal(|| false); - - let todos = cx.props.todos.get(); - let todo = &todos[&cx.props.id]; - let completed = if todo.checked { "completed" } else { "" }; - let editing = if **is_editing { "editing" } else { "" }; + let checked = use_selector(move || todos().get(&id).unwrap().checked); + let contents = use_selector(move || todos().get(&id).unwrap().contents.clone()); rsx! { - li { class: "{completed} {editing}", + li { class: if *checked() { "completed" }, class: if *is_editing() { "editing"}, div { class: "view", input { class: "toggle", r#type: "checkbox", - id: "cbg-{todo.id}", - checked: "{todo.checked}", - oninput: move |evt| { - cx.props.todos.make_mut()[&cx.props.id].checked = evt.value().parse().unwrap(); - } + id: "cbg-{id}", + checked: "{checked}", + oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.value().parse().unwrap(), } label { - r#for: "cbg-{todo.id}", + r#for: "cbg-{id}", ondoubleclick: move |_| is_editing.set(true), prevent_default: "onclick", - "{todo.contents}" + "{contents}" } button { class: "destroy", - onclick: move |_| { - cx.props.todos.make_mut().remove(&todo.id); - }, + onclick: move |_| { todos.write().remove(&id); }, prevent_default: "onclick" } } - if **is_editing { + if *is_editing() { input { class: "edit", - value: "{todo.contents}", - oninput: move |evt| cx.props.todos.make_mut()[&cx.props.id].contents = evt.value(), + value: "{contents}", + oninput: move |evt| todos.write().get_mut(&id).unwrap().contents = evt.value(), autofocus: "true", onfocusout: move |_| is_editing.set(false), onkeydown: move |evt| { @@ -183,39 +152,32 @@ pub fn TodoEntry(props: TodoEntryProps) -> Element { Key::Enter | Key::Escape | Key::Tab => is_editing.set(false), _ => {} } - }, + } } } } } } -#[derive(Props)] -pub struct ListFooterProps<'a> { - todos: &'a UseState>, - active_todo_count: usize, - active_todo_text: &'a str, - show_clear_completed: bool, - filter: &'a UseState, -} - -pub fn ListFooter(props: ListFooterProps) -> Element { - let active_todo_count = cx.props.active_todo_count; - let active_todo_text = cx.props.active_todo_text; - - let selected = |state| { - if *cx.props.filter == state { - "selected" - } else { - "false" - } - }; +#[component] +pub fn ListFooter( + todos: Signal>, + active_todo_count: ReadOnlySignal, + filter: Signal, +) -> Element { + let show_clear_completed = use_selector(move || todos().values().any(|todo| todo.checked)); rsx! { footer { class: "footer", span { class: "todo-count", strong { "{active_todo_count} " } - span { "{active_todo_text} left" } + span { + match *active_todo_count() { + 1 => "item", + _ => "items", + } + " left" + } } ul { class: "filters", for (state , state_text , url) in [ @@ -226,18 +188,18 @@ pub fn ListFooter(props: ListFooterProps) -> Element { li { a { href: url, - class: selected(state), - onclick: move |_| cx.props.filter.set(state), + class: if *filter() == state { "selected" }, + onclick: move |_| filter.set(state), prevent_default: "onclick", {state_text} } } } } - if cx.props.show_clear_completed { + if *show_clear_completed() { button { class: "clear-completed", - onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked), + onclick: move |_| todos.write().retain(|_, todo| !todo.checked), "Clear completed" } }