Function leptos::provide_context

source ·
pub fn provide_context<T>(value: T)
where T: Clone + 'static,
Expand description

Provides a context value of type T to the current reactive node and all of its descendants. This can be consumed using use_context.

This is useful for passing values down to components or functions lower in a hierarchy without needs to “prop drill” by passing them through each layer as arguments to a function or properties of a component.

Context works similarly to variable scope: a context that is provided higher in the reactive graph can be used lower down, but a context that is provided lower down cannot be used higher up.

use leptos::*;

// define a newtype we'll provide as context
// contexts are stored by their types, so it can be useful to create
// a new type to avoid confusion with other `WriteSignal<i32>`s we may have
// all types to be shared via context should implement `Clone`
#[derive(Copy, Clone)]
struct ValueSetter(WriteSignal<i32>);

#[component]
pub fn Provider() -> impl IntoView {
    let (value, set_value) = create_signal(0);

    // 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(ValueSetter(set_value));

    // because <Consumer/> is nested inside <Provider/>,
    // it has access to the provided context
    view! { <div><Consumer/></div> }
}

#[component]
pub fn Consumer() -> impl IntoView {
    // consume the provided context of type `ValueSetter` using `use_context`
    // this traverses up the reactive graph and gets the nearest provided `ValueSetter`
    let set_value = use_context::<ValueSetter>().unwrap().0;
}

§Warning: Shadowing Context Correctly

The reactive graph exists alongside the component tree. Generally speaking, context provided by a parent component can be accessed by its children and other descendants, and not vice versa. But components do not exist at runtime: a parent and children that are all rendered unconditionally exist in the same reactive scope.

This can have unexpected effects on context: namely, children can sometimes override contexts provided by their parents, including for their siblings, if they “shadow” context by providing another context of the same kind.

use leptos::*;

#[component]
fn Parent() -> impl IntoView {
    provide_context("parent_context");
    view! {
        <Child /> // this is receiving "parent_context" as expected
        <Child /> // but this is receiving "child_context" instead of "parent_context"!
    }
}

#[component]
fn Child() -> impl IntoView {
    // first, we receive context from parent (just before the override)
    let context = expect_context::<&'static str>();
    // then we provide context under the same type
    provide_context("child_context");
    view! {
        <div>{format!("child (context: {context})")}</div>
    }
}

In this case, neither of the children is rendered dynamically, so there is no wrapping effect created around either. All three components here have the same reactive owner, so providing a new context of the same type in the first <Child/> overrides the context that was provided in <Parent/>, meaning that the second <Child/> receives the context from its sibling instead.

§Solution

If you are using the full Leptos framework, you can use the Provider component to solve this issue.

#[component]
fn Child() -> impl IntoView {
    let context = expect_context::<&'static str>();
    // creates a new reactive node, which means the context will
    // only be provided to its children, not modified in the parent
    view! {
        <Provider value="child_context">
            <div>{format!("child (context: {context})")}</div>
        </Provider>
    }
}

§Alternate Solution

This can also be solved by introducing some additional reactivity. In this case, it’s simplest to simply make the body of <Child/> a function, which means it will be wrapped in a new reactive node when rendered:

#[component]
fn Child() -> impl IntoView {
    let context = expect_context::<&'static str>();
    // creates a new reactive node, which means the context will
    // only be provided to its children, not modified in the parent
    move || {
        provide_context("child_context");
        view! {
            <div>{format!("child (context: {context})")}</div>
        }
    }
}

This is equivalent to the difference between two different forms of variable shadowing in ordinary Rust:

// shadowing in a flat hierarchy overrides value for siblings
// <Parent/>: declares variable
let context = "parent_context";
// First <Child/>: consumes variable, then shadows
println!("{context:?}");
let context = "child_context";
// Second <Child/>: consumes variable, then shadows
println!("{context:?}");
let context = "child_context";

// but shadowing in nested scopes works as expected
// <Parent/>
let context = "parent_context";

// First <Child/>
{
    println!("{context:?}");
    let context = "child_context";
}

// Second <Child/>
{
    println!("{context:?}");
    let context = "child_context";
}