yewstack / yew

Rust / Wasm framework for creating reliable and efficient web applications
https://yew.rs
Apache License 2.0
30.53k stars 1.42k forks source link

Inconsistent context examples #2927

Closed allan2 closed 11 months ago

allan2 commented 1 year ago

When trying to learn contexts, I got stuck on the Docusaurus example. I had to reach for the Yew API docs (a.k.a. cargo docs for Yew Next) to get things working. That's when I noticed that the docs are inconsistent on this topic.

The first example is found in docs/concepts/contexts.mdx, which is hosted here on yew.rs. The second example is on the use_context page in the Yew Next cargo docs.

Example 1:

#[derive(Clone, Debug, PartialEq)]
struct Theme {
    foreground: String,
    background: String,
}

#[function_component]
fn App() -> Html {
    let theme = use_memo(|_| Theme {
        foreground: "yellow".to_owned(),
        background: "pink".to_owned(),
    }, ());

    html! {
        <ContextProvider<Rc<Theme>> context={theme}>
            <NavButton />
        </ContextProvider<Rc<Theme>>>
    }
}

#[function_component]
fn NavButton() -> Html {
    let theme = use_context::<Theme>();

    html! {
        // use theme
    }
}

Example 2 (snippet):

#[derive(Clone, Debug, PartialEq)]
struct Theme {
    foreground: String,
    background: String,
}

#[function_component]
pub fn App() -> Html {
    let ctx = use_state(|| Theme {
        foreground: "#000000".to_owned(),
        background: "#eeeeee".to_owned(),
    });

    html! {
        // `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
        // so we deref it.
        // It derefs to `&Theme`, hence the clone
        <ContextProvider<Theme> context={(*ctx).clone()}>
            // Every child here and their children will have access to this context.
            <Toolbar />
        </ContextProvider<Theme>>
    }
}

/// The toolbar.
/// This component has access to the context
#[function_component]
pub fn Toolbar() -> Html {
    html! {
        <div>
            <ThemedButton />
        </div>
    }
}

/// Button placed in `Toolbar`.
/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context.
#[function_component]
pub fn ThemedButton() -> Html {
    let theme = use_context::<Theme>().expect("no ctx found");

    html! {
        <button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
            { "Click me!" }
        </button>
    }
}

Example 1 uses use_memo and ContextProvider<Rc<Theme>>. Example 2 uses use_state and ContextProvider<Theme>.

I suspect Example 1 is either incomplete or out-of-date because theme in NavButton has type Option<Theme>, which is not mentioned. It also has a None value if you try to unwrap, which is where I got stuck. Example 2 works fine.

It would be nice if the context docs were consistent. I am not still not sure whether use_memo or use_state is preferred or if both can be used, but an explanation would be helpful.

ClementGre commented 1 year ago

Hi, I agree with you. I couldn't get the first example to work. use_context always returned None.

Also, the second example causes a performance problem as the context struct Theme will be cloned for each child using it. I am currently implementing a translation system with a struct that I want to store on the state, and it is just not right to clone all of my Translator struct, with all of the loaded translations data, for each child.

Then I am wondering if Yew has the ability to support reference-based contexts, just like in example 1 (but it is not working), or should I use an external library?