Component Children
It’s pretty common to want to pass children into a component, just as you can pass
children into an HTML element. For example, imagine I have a <FancyForm/>
component
that enhances an HTML <form>
. I need some way to pass all its inputs.
view! {
<FancyForm>
<fieldset>
<label>
"Some Input"
<input type="text" name="something"/>
</label>
</fieldset>
<button>"Submit"</button>
</FancyForm>
}
How can you do this in Leptos? There are basically two ways to pass components to other components:
- render props: properties that are functions that return a view
- the
children
prop: a special component property that includes anything you pass as a child to the component.
In fact, you’ve already seen these both in action in the <Show/>
component:
view! {
<Show
// `when` is a normal prop
when=move || value.get() > 5
// `fallback` is a "render prop": a function that returns a view
fallback=|| view! { <Small/> }
>
// `<Big/>` (and anything else here)
// will be given to the `children` prop
<Big/>
</Show>
}
Let’s define a component that takes some children and a render prop.
/// Displays a `render_prop` and some children within markup.
#[component]
pub fn TakesChildren<F, IV>(
/// Takes a function (type F) that returns anything that can be
/// converted into a View (type IV)
render_prop: F,
/// `children` can take one of several different types, each of which
/// is a function that returns some view type
children: Children,
) -> impl IntoView
where
F: Fn() -> IV,
IV: IntoView,
{
view! {
<h1><code>"<TakesChildren/>"</code></h1>
<h2>"Render Prop"</h2>
{render_prop()}
<hr/>
<h2>"Children"</h2>
{children()}
}
}
render_prop
and children
are both functions, so we can call them to generate
the appropriate views. children
, in particular, is an alias for
Box<dyn FnOnce() -> AnyView>
. (Aren't you glad we named it Children
instead?)
The AnyView
returned here is an opaque, type-erased view: you can’t do anything to
inspect it. There are a variety of other child types: for example, ChildrenFragment
will return a Fragment
, which is a collection whose children can be iterated over.
If you need a
Fn
orFnMut
here because you need to callchildren
more than once, we also provideChildrenFn
andChildrenMut
aliases.
We can use the component like this:
view! {
<TakesChildren render_prop=|| view! { <p>"Hi, there!"</p> }>
// these get passed to `children`
"Some text"
<span>"A span"</span>
</TakesChildren>
}
Typed Children: Slots
So far, we have discussed components with a single children
prop, but sometimes it is helpful to create a component with multiple children of different types. For example:
view! {
<If condition=a_is_true>
<Then>"Show content when a is true"</Then>
<ElseIf condition=b_is_true>"b is true"</ElseIf>
<ElseIf condition=c_is_true>"c is true"</ElseIf>
<Else>"None of the above are true"</Else>
</If>
}
The If
component always expects a Then
child, optionally multiple ElseIf
children and an optional Else
child. To handle this, Leptos provides slots.
The #[slot]
macro annotates a plain Rust struct as component slot:
// A simple struct annotated with `#[slot]`,
// which expects children
#[slot]
struct Then {
children: ChildrenFn,
}
This slot can be used as a prop in a component:
#[component]
fn If(
condition: Signal<bool>,
// Component slot, should be passed through the <Then slot> syntax
then_slot: Then,
) -> impl IntoView {
move || {
if condition.get() {
(then_slot.children)().into_any()
} else {
().into_any()
}
}
}
Now, the If
component expects a child of type Then
. You would need to annotate the used slot with slot:<prop_name>
:
view! {
<If condition=a_is_true>
// The `If` component always expects a `Then` child for `then_slot`
<Then slot:then_slot>"Show content when a is true"</Then>
</If>
}
Specifying
slot
without a name will default the chosen slot as the snake case version of the struct name. So in this case<Then slot>
would be equivalent to<Then slot:then>
.
For the complete example, see slots examples.
Event handlers on slots
Event handlers cannot be specified directly on slots like this:
<ComponentWithSlot>
// ⚠️ Event handler `on:click` directly on slot is not allowed
<SlotWithChildren slot:slot on:click=move |_| {}>
<h1>"Hello, World!"</h1>
</SlotWithChildren>
</ComponentWithSlot>
Instead, wrap the slot content in a regular element and attach event handlers there:
<ComponentWithSlot>
<SlotWithChildren slot:slot>
// ✅ Event handler is not defined directly on slot
<div on:click=move |_| {}>
<h1>"Hello, World!"</h1>
</div>
</SlotWithChildren>
</ComponentWithSlot>
Manipulating Children
The Fragment
type is
basically a way of wrapping a Vec<AnyView>
. You can insert it anywhere into your view.
But you can also access those inner views directly to manipulate them. For example, here’s a component that takes its children and turns them into an unordered list.
/// Wraps each child in an `<li>` and embeds them in a `<ul>`.
#[component]
pub fn WrapsChildren(children: ChildrenFragment) -> impl IntoView {
// children() returns a `Fragment`, which has a
// `nodes` field that contains a Vec<View>
// this means we can iterate over the children
// to create something new!
let children = children()
.nodes
.into_iter()
.map(|child| view! { <li>{child}</li> })
.collect::<Vec<_>>();
view! {
<h1><code>"<WrapsChildren/>"</code></h1>
// wrap our wrapped children in a UL
<ul>{children}</ul>
}
}
Calling it like this will create a list:
view! {
<WrapsChildren>
"A"
"B"
"C"
</WrapsChildren>
}
Live example
CodeSandbox Source
use leptos::prelude::*;
// 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 {
let (items, set_items) = signal(vec![0, 1, 2]);
let render_prop = move || {
let len = move || items.read().len();
view! {
<p>"Length: " {len}</p>
}
};
view! {
// This component just displays the two kinds of children,
// embedding them in some other markup
<TakesChildren
// for component props, you can shorthand
// `render_prop=render_prop` => `render_prop`
// (this doesn't work for HTML element attributes)
render_prop
>
// these look just like the children of an HTML element
<p>"Here's a child."</p>
<p>"Here's another child."</p>
</TakesChildren>
<hr/>
// This component actually iterates over and wraps the children
<WrapsChildren>
<p>"Here's a child."</p>
<p>"Here's another child."</p>
</WrapsChildren>
}
}
/// Displays a `render_prop` and some children within markup.
#[component]
pub fn TakesChildren<F, IV>(
/// 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<dyn FnOnce() -> Fragment>`
/// ... aren't you glad we named it `Children` instead?
children: Children,
) -> impl IntoView
where
F: Fn() -> IV,
IV: IntoView,
{
view! {
<h1><code>"<TakesChildren/>"</code></h1>
<h2>"Render Prop"</h2>
{render_prop()}
<hr/>
<h2>"Children"</h2>
{children()}
}
}
/// Wraps each child in an `<li>` and embeds them in a `<ul>`.
#[component]
pub fn WrapsChildren(children: ChildrenFragment) -> impl IntoView {
// children() returns a `Fragment`, which has a
// `nodes` field that contains a Vec<View>
// this means we can iterate over the children
// to create something new!
let children = children()
.nodes
.into_iter()
.map(|child| view! { <li>{child}</li> })
.collect::<Vec<_>>();
view! {
<h1><code>"<WrapsChildren/>"</code></h1>
// wrap our wrapped children in a UL
<ul>{children}</ul>
}
}
fn main() {
leptos::mount::mount_to_body(App)
}