diff --git a/smart-house-web/frontend/src/main.rs b/smart-house-web/frontend/src/main.rs index 86338a8..50fccde 100644 --- a/smart-house-web/frontend/src/main.rs +++ b/smart-house-web/frontend/src/main.rs @@ -1,61 +1,156 @@ +#![allow(non_snake_case)] use leptos::prelude::*; -#[allow(non_snake_case)] -// Composing different components together is how we build -// user interfaces. Here, we'll define a reusable . -// You'll see how doc comments can be used to document components -// and their properties. - -/// Shows progress toward a goal. -#[component] -fn ProgressBar( - // Marks this as an optional prop. It will default to the default - // value of its type, i.e., 0. - #[prop(default = 100)] - /// The maximum value of the progress bar. - max: u16, - // Will run `.into()` on the value passed into the prop. - #[prop(into)] - // `Signal` is a wrapper for several reactive types. - // It can be helpful in component APIs like this, where we - // might want to take any kind of reactive value - /// How much progress should be displayed. - progress: Signal, -) -> impl IntoView { - view! { - -
- } -} +// Iteration is a very common task in most applications. +// So how do you take a list of data and render it in the DOM? +// This example will show you the two ways: +// 1) for mostly-static lists, using Rust iterators +// 2) for lists that grow, shrink, or move items, using #[component] fn App() -> impl IntoView { - let (count, set_count) = signal(0); + view! { +

"Iteration"

+

"Static List"

+

"Use this pattern if the list itself is static."

+ +

"Dynamic List"

+

"Use this pattern if the rows in your list will change."

+ + } +} - let double_count = move || count.get() * 2; +/// A list of counters, without the ability +/// to add or remove any. +#[component] +fn StaticList( + /// How many counters to include in this list. + length: usize, +) -> impl IntoView { + // create counter signals that start at incrementing numbers + let counters = (1..=length).map(|idx| RwSignal::new(idx)); + + // when you have a list that doesn't change, you can + // manipulate it using ordinary Rust iterators + // and collect it into a Vec<_> to insert it into the DOM + let counter_buttons = counters + .map(|count| { + view! { +
  • + +
  • + } + }) + .collect::>(); + + // Note that if `counter_buttons` were a reactive list + // and its value changed, this would be very inefficient: + // it would rerender every row every time the list changed. + view! { +
      {counter_buttons}
    + } +} + +/// A list of counters that allows you to add or +/// remove counters. +#[component] +fn DynamicList( + /// The number of counters to begin with. + initial_length: usize, +) -> impl IntoView { + // This dynamic list will use the component. + // is a keyed list. This means that each row + // has a defined key. If the key does not change, the row + // will not be re-rendered. When the list changes, only + // the minimum number of changes will be made to the DOM. + + // `next_counter_id` will let us generate unique IDs + // we do this by simply incrementing the ID by one + // each time we create a counter + let mut next_counter_id = initial_length; + + // we generate an initial list as in + // but this time we include the ID along with the signal + // see NOTE in add_counter below re: ArcRwSignal + let initial_counters = (0..initial_length) + .map(|id| (id, ArcRwSignal::new(id + 1))) + .collect::>(); + + // now we store that initial list in a signal + // this way, we'll be able to modify the list over time, + // adding and removing counters, and it will change reactively + let (counters, set_counters) = signal(initial_counters); + + let add_counter = move |_| { + // create a signal for the new counter + // we use ArcRwSignal here, instead of RwSignal + // ArcRwSignal is a reference-counted type, rather than the arena-allocated + // signal types we've been using so far. + // When we're creating a collection of signals like this, using ArcRwSignal + // allows each signal to be deallocated when its row is removed. + let sig = ArcRwSignal::new(next_counter_id + 1); + // add this counter to the list of counters + set_counters.update(move |counters| { + // since `.update()` gives us `&mut T` + // we can just use normal Vec methods like `push` + counters.push((next_counter_id, sig)) + }); + // increment the ID so it's always unique + next_counter_id += 1; + }; view! { - -
    - // If you have this open in CodeSandbox or an editor with - // rust-analyzer support, try hovering over `ProgressBar`, - // `max`, or `progress` to see the docs we defined above - - // Let's use the default max value on this one - // the default is 100, so it should move half as fast - - // Signal::derive creates a Signal wrapper from our derived signal - // using double_count means it should move twice as fast - +
    + +
      + // The component is central here + // This allows for efficient, key list rendering + instead of + each=move || counters.get() + // the key should be unique and stable for each row + // using an index is usually a bad idea, unless your list + // can only grow, because moving items around inside the list + // means their indices will change and they will all rerender + key=|counter| counter.0 + // `children` receives each item from your `each` iterator + // and returns a view + children=move |(id, count)| { + // we can convert our ArcRwSignal to a Copy-able RwSignal + // for nicer DX when moving it into the view + let count = RwSignal::from(count); + view! { +
    • + + +
    • + } + } + /> +
    +
    } }