Projecting Children

As you build components you may occasionally find yourself wanting to “project” children through multiple layers of components.

The Problem

Consider the following:

pub fn LoggedIn<F, IV>(fallback: F, children: ChildrenFn) -> impl IntoView
where
    F: Fn() -> IV + 'static,
    IV: IntoView,
{
    view! {
        <Suspense
            fallback=|| ()
        >
            <Show
				// check whether user is verified
				// by reading from the resource
                when=move || todo!()
                fallback=fallback
            >
				{children()}
			</Show>
        </Suspense>
    }
}

This is pretty straightforward: when the user is logged in, we want to show children. If the user is not logged in, we want to show fallback. And while we’re waiting to find out, we just render (), i.e., nothing.

In other words, we want to pass the children of <LoggedIn/> through the <Suspense/> component to become the children of the <Show/>. This is what I mean by “projection.”

This won’t compile.

error[E0507]: cannot move out of `fallback`, a captured variable in an `Fn` closure
error[E0507]: cannot move out of `children`, a captured variable in an `Fn` closure

The problem here is that both <Suspense/> and <Show/> need to be able to construct their children multiple times. The first time you construct <Suspense/>’s children, it would take ownership of fallback and children to move them into the invocation of <Show/>, but then they're not available for future <Suspense/> children construction.

The Details

Feel free to skip ahead to the solution.

If you want to really understand the issue here, it may help to look at the expanded view macro. Here’s a cleaned-up version:

Suspense(
    ::leptos::component_props_builder(&Suspense)
        .fallback(|| ())
        .children({
            // fallback and children are moved into this closure
            Box::new(move || {
                {
                    // fallback and children captured here
                    leptos::Fragment::lazy(|| {
                        vec![
                            (Show(
                                ::leptos::component_props_builder(&Show)
                                    .when(|| true)
									// but fallback is moved into Show here
                                    .fallback(fallback)
									// and children is moved into Show here
                                    .children(children)
                                    .build(),
                            )
                            .into_view()),
                        ]
                    })
                }
            })
        })
        .build(),
)

All components own their props; so the <Show/> in this case can’t be called because it only has captured references to fallback and children.

Solution

However, both <Suspense/> and <Show/> take ChildrenFn, i.e., their children should implement the Fn type so they can be called multiple times with only an immutable reference. This means we don’t need to own children or fallback; we just need to be able to pass 'static references to them.

We can solve this problem by using the store_value primitive. This essentially stores a value in the reactive system, handing ownership off to the framework in exchange for a reference that is, like signals, Copy and 'static, which we can access or modify through certain methods.

In this case, it’s really simple:

pub fn LoggedIn<F, IV>(fallback: F, children: ChildrenFn) -> impl IntoView
where
    F: Fn() -> IV + 'static,
    IV: IntoView,
{
    let fallback = store_value(fallback);
    let children = store_value(children);
    view! {
        <Suspense
            fallback=|| ()
        >
            <Show
                when=|| todo!()
                fallback=move || fallback.with_value(|fallback| fallback())
            >
                {children.with_value(|children| children())}
            </Show>
        </Suspense>
    }
}

At the top level, we store both fallback and children in the reactive scope owned by LoggedIn. Now we can simply move those references down through the other layers into the <Show/> component and call them there.

A Final Note

Note that this works because <Show/> and <Suspense/> only need an immutable reference to their children (which .with_value can give it), not ownership.

In other cases, you may need to project owned props through a function that takes ChildrenFn and therefore needs to be called more than once. In this case, you may find the clone: helper in theview macro helpful.

Consider this example

#[component]
pub fn App() -> impl IntoView {
    let name = "Alice".to_string();
    view! {
        <Outer>
            <Inner>
                <Inmost name=name.clone()/>
            </Inner>
        </Outer>
    }
}

#[component]
pub fn Outer(children: ChildrenFn) -> impl IntoView {
    children()
}

#[component]
pub fn Inner(children: ChildrenFn) -> impl IntoView {
    children()
}

#[component]
pub fn Inmost(name: String) -> impl IntoView {
    view! {
        <p>{name}</p>
    }
}

Even with name=name.clone(), this gives the error

cannot move out of `name`, a captured variable in an `Fn` closure

It’s captured through multiple levels of children that need to run more than once, and there’s no obvious way to clone it into the children.

In this case, the clone: syntax comes in handy. Calling clone:name will clone name before moving it into <Inner/>’s children, which solves our ownership issue.

view! {
	<Outer>
		<Inner clone:name>
			<Inmost name=name.clone()/>
		</Inner>
	</Outer>
}

These issues can be a little tricky to understand or debug, because of the opacity of the view macro. But in general, they can always be solved.