diff --git a/smart-house-web/frontend/src/main.rs b/smart-house-web/frontend/src/main.rs index 57c11e5..b23fe39 100644 --- a/smart-house-web/frontend/src/main.rs +++ b/smart-house-web/frontend/src/main.rs @@ -1,114 +1,89 @@ #![allow(non_snake_case)] -use leptos::{ev::MouseEvent, prelude::*}; +use leptos::prelude::*; -// This highlights four different ways that child components can communicate -// with their parent: -// 1) : passing a WriteSignal as one of the child component props, -// for the child component to write into and the parent to read -// 2) : passing a closure as one of the child component props, for -// the child component to call -// 3) : adding an `on:` event listener to a component -// 4) : providing a context that is used in the component (rather than prop drilling) - -#[derive(Copy, Clone)] -struct SmallcapsContext(WriteSignal); +// Often, you want to pass some kind of child view to another +// component. There are two basic patterns for doing this: +// - "render props": creating a component prop that takes a function +// that creates a view +// - the `children` prop: a special property that contains content +// passed as the children of a component in your view, not as a +// property #[component] pub fn App() -> impl IntoView { - // just some signals to toggle four classes on our

- let (red, set_red) = signal(false); - let (right, set_right) = signal(false); - let (italics, set_italics) = signal(false); - let (smallcaps, set_smallcaps) = signal(false); - - // the newtype pattern isn't *necessary* here but is a good practice - // it avoids confusion with other possible future `WriteSignal` contexts - // and makes it easier to refer to it in ButtonD - provide_context(SmallcapsContext(set_smallcaps)); + let (items, set_items) = signal(vec![0, 1, 2]); + let render_prop = move || { + let len = move || items.read().len(); + view! { +

"Length: " {len}

+ } + }; view! { -
-

bool, and these signals all implement Fn() - class:red=red - class:right=right - class:italics=italics - class:smallcaps=smallcaps - > - "Lorem ipsum sit dolor amet." -

- - // Button A: pass the signal setter - - - // Button B: pass a closure - - - // Button C: use a regular event listener - // setting an event listener on a component like this applies it - // to each of the top-level elements the component returns - - - // Button D gets its setter from context rather than props - -
- } -} - -/// Button A receives a signal setter and updates the signal itself -#[component] -pub fn ButtonA( - /// Signal that will be toggled when the button is clicked. - setter: WriteSignal, -) -> impl IntoView { - view! { - + // these look just like the children of an HTML element +

"Here's a child."

+

"Here's another child."

+ +
+ // This component actually iterates over and wraps the children + +

"Here's a child."

+

"Here's another child."

+
} } -/// Button B receives a closure +/// Displays a `render_prop` and some children within markup. #[component] -pub fn ButtonB( - /// Callback that will be invoked when the button is clicked. - on_click: impl FnMut(MouseEvent) + 'static, -) -> impl IntoView { +pub fn TakesChildren( + /// Takes a function (type F) that returns anything that can be + /// converted into a View (type IV) + render_prop: F, + /// `children` takes the `Children` type + /// this is an alias for `Box Fragment>` + /// ... aren't you glad we named it `Children` instead? + children: Children, +) -> impl IntoView +where + F: Fn() -> IV, + IV: IntoView, +{ view! { - +

""

+

"Render Prop"

+ {render_prop()} +
+

"Children"

+ {children()} } } -/// Button C is a dummy: it renders a button but doesn't handle -/// its click. Instead, the parent component adds an event listener. +/// Wraps each child in an `
  • ` and embeds them in a `
      `. #[component] -pub fn ButtonC() -> impl IntoView { - view! { - - } -} - -/// Button D is very similar to Button A, but instead of passing the setter as a prop -/// we get it from the context -#[component] -pub fn ButtonD() -> impl IntoView { - let setter = use_context::().unwrap().0; +pub fn WrapsChildren(children: ChildrenFragment) -> impl IntoView { + // children() returns a `Fragment`, which has a + // `nodes` field that contains a Vec + // this means we can iterate over the children + // to create something new! + let children = children() + .nodes + .into_iter() + .map(|child| view! {
    • {child}
    • }) + .collect::>(); view! { - +

      ""

      + // wrap our wrapped children in a UL +
        {children}
      } }