Example of four modes of parent-child communication between components

This commit is contained in:
Greg Johnston 2022-10-19 07:35:22 -04:00
parent 0280eaf1cb
commit 4448f7a83a
6 changed files with 172 additions and 0 deletions

View File

@ -19,6 +19,7 @@ exclude = [
"examples/hackernews/hackernews-app",
"examples/hackernews/hackernews-client",
"examples/hackernews/hackernews-server",
"examples/parent-child",
"examples/router",
"examples/todomvc",
"examples/todomvc-ssr/todomvc-ssr-client",

View File

@ -0,0 +1,14 @@
[package]
name = "parent-child"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
console_log = "0.2"
log = "0.4"
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
<style>
.red {
color: red;
}
.right {
text-align: right;
}
.italics {
font-style: italic;
}
.smallcaps {
font-variant: small-caps;
}
</style>
</head>
<body></body>
</html>

View File

@ -0,0 +1,124 @@
use leptos::*;
use web_sys::Event;
// This highlights four different ways that child components can communicate
// with their parent:
// 1) <ButtonA/>: passing a WriteSignal as one of the child component props,
// for the child component to write into and the parent to read
// 2) <ButtonB/>: passing a closure as one of the child component props, for
// the child component to call
// 3) <ButtonC/>: adding a simple event listener on the child component itself
// 4) <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
#[derive(Copy, Clone)]
struct SmallcapsContext(WriteSignal<bool>);
#[component]
pub fn App(cx: Scope) -> Element {
// just some signals to toggle three classes on our <p>
let (red, set_red) = create_signal(cx, false);
let (right, set_right) = create_signal(cx, false);
let (italics, set_italics) = create_signal(cx, false);
let (smallcaps, set_smallcaps) = create_signal(cx, false);
// the newtype pattern isn't *necessary* here but is a good practice
// it avoids confusion with other possible future `WriteSignal<bool>` contexts
// and makes it easier to refer to it in ButtonD
provide_context(cx, SmallcapsContext(set_smallcaps));
view! {
cx,
<main>
<p
// class: attributes take F: Fn() => bool, and these signals all implement Fn()
class:red=red
class:right=right
class:italics=italics
class:smallcaps=smallcaps
>
"Lorem ipsum sit dolor amet."
</p>
// Button A: pass the signal setter
<ButtonA setter=set_red/>
// Button B: pass a closure
<ButtonB on_click=move |_| set_right.update(|value| *value = !*value)/>
// Button C: components that return an Element, like elements, can take on: event handler attributes
<ButtonC on:click=move |_| set_italics.update(|value| *value = !*value)/>
// Button D gets its setter from context rather than props
<ButtonD/>
</main>
}
}
// Button A receives a signal setter and updates the signal itself
#[component]
pub fn ButtonA(cx: Scope, setter: WriteSignal<bool>) -> Element {
view! {
cx,
<button
on:click=move |_| setter.update(|value| *value = !*value)
>
"Toggle Red"
</button>
}
}
// Button B receives a closure
#[component]
pub fn ButtonB<F>(cx: Scope, on_click: F) -> Element
where
F: Fn(Event) + 'static,
{
view! {
cx,
<button
on:click=on_click
>
"Toggle Right"
</button>
}
// just a note: in an ordinary function ButtonB could take on_click: impl Fn(Event) + 'static
// and save you from typing out the generic
// the component macro actually expands to define a
//
// struct ButtonBProps<F> where F: Fn(Event) + 'static {
// on_click: F
// }
//
// this is what allows us to have named props in our component invocation,
// instead of an ordered list of function arguments
// if Rust ever had named function arguments we could drop this requirement
}
// Button C will have its event listener added by the parent
// This is just a way of encapsulating whatever markup you need for the button
#[component]
pub fn ButtonC(cx: Scope) -> Element {
view! {
cx,
<button>
"Toggle Italics"
</button>
}
}
// 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(cx: Scope) -> Element {
let setter = use_context::<SmallcapsContext>(cx).unwrap().0;
view! {
cx,
<button
on:click=move |_| setter.update(|value| *value = !*value)
>
"Toggle Small Caps"
</button>
}
}

View File

@ -0,0 +1,7 @@
use leptos::*;
use parent_child::*;
pub fn main() {
// _ = console_log::init_with_level(log::Level::Debug);
mount_to_body(|cx| view! { cx, <App/> })
}

View File

@ -19,6 +19,8 @@
//! counter example, showing the basics of client-side rendering and reactive DOM updates
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counter) introduces parent-child
//! communication via contexts, and the `<For/>` component for efficient keyed list updates.
//! - [`parent-child`](https://github.com/gbj/leptos/tree/main/examples/parent-child) shows four different
//! ways a parent component can communicate with a child, including passing a closure, context, and more
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) implements the classic to-do
//! app in Leptos. This is a good example of a complete, simple app. In particular, you might want to
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/gbj/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)