Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The display is inconsistent due to the location of provide_context. #2038

Closed
luoxiaozero opened this issue Nov 17, 2023 · 4 comments
Closed

Comments

@luoxiaozero
Copy link
Contributor

Describe the bug

The display is inconsistent due to the location of provide_context.

Leptos Dependencies

Please copy and paste the Leptos dependencies and features from your Cargo.toml.

For example:

leptos = { version = "0.5.2", features = ["csr"] }

Modified at CodeSandbox at https://leptos-rs.github.io/leptos/view/08_parent_child.html

use leptos::*;

#[derive(Copy, Clone)]
struct SmallcapsContext(WriteSignal<bool>);

#[component]
pub fn App() -> impl IntoView {
    let (red, set_red) = create_signal(false);
    provide_context(SmallcapsContext(set_red));

    view! {
        <main>
            // here!!!
            <div>
                <p class:red=red>"apple"</p>
                <Button/>
            </div>
            <App2/>
        </main>
    }
}

#[component]
pub fn Button() -> impl IntoView {
    let setter = use_context::<SmallcapsContext>().unwrap().0;

    view! {
        <button on:click=move |_| {
            setter.update(|value| *value = !*value)
        }>"Toggle Red"</button>
    }
}

#[component]
fn App2() -> impl IntoView {
    let (red, set_red) = create_signal(false);
    provide_context(SmallcapsContext(set_red));

    view! {
        <main>
            <p class:red=red>
                "apple2"
            </p>
            <Button2/>
        </main>
    }
}

#[component]
pub fn Button2() -> impl IntoView {
    let setter = use_context::<SmallcapsContext>().unwrap().0;
    
    view! {
        <button on:click=move |_| {
            setter.update(|value| *value = !*value)
        }>"2. Toggle Red"</button>
    }
}

fn main() {
    leptos::mount_to_body(App)
}

To Reproduce
Steps to reproduce the behavior:

Change the position of div and App2 under the main tag.

  1. Click the Button Button The apple text turns red. Click the Button2 Button The apple2 text turns red.
<div>
  <p class:red=red>"Lorem ipsum sit dolor amet."</p>
  <Button/>
</div>
<App2/>
  1. Click the Button Button The apple2 text turns red. Click the Button2 Button The apple2 text turns red.
<App2/>
<div>
  <p class:red=red>"Lorem ipsum sit dolor amet."</p>
  <Button/>
</div>

Expected behavior

Regardless of location, you should:

Click the Button Button The apple text turns red. Click the Button2 Button The apple2 text turns red.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

@gbj
Copy link
Collaborator

gbj commented Nov 17, 2023

This is working as designed. <App2/> "shadows" the context of the same type that you'd already provided. Context in 0.5 no longer follows the component tree, but the reactive graph. See discussion of the same thing in #1986 and edited docs in #2015.

Here is a version of <App2/> that works as intended. See the above issue and PR for explanation:

#[component]
fn App2() -> impl IntoView {
    let (red, set_red) = create_signal(false);
    move || {
        provide_context(SmallcapsContext(set_red));

        view! {
            <main>
                <p class:red=red>
                    "apple2"
                </p>
                <Button2/>
            </main>
        }
    }
}

@luoxiaozero
Copy link
Contributor Author

I understand, can we provide a Provider component?

  1. For users, the provide context of vue react solid is to inject values into descendant components. For users, leptos is based on reactive graph, which is easy to cause mental burden (although there is documentation now).
  2. Using DydChild to solve problems has mental burden and performance loss, and the child must be Fn instead of FnOnce.

@gbj
Copy link
Collaborator

gbj commented Nov 18, 2023

Yes — providing a Provider component is a good idea and the correct solution. For me, this is another one of those "ahhh, now I understand why they do it this way" moments. Once we dropped explicit scope a Provider becomes a good idea for this reason.

Luckily it is very easy to implement:

#[component]
pub fn ContextProvider<T>(value: T, children: Children) -> impl IntoView
where
    T: Clone + 'static,
{
    run_as_child(move || {
        provide_context(value);
        children()
    })
}

I have tested this against your example and it works as expected with the <div> and <App2/> in either arrangement.

#[derive(Copy, Clone)]
struct SmallcapsContext(WriteSignal<bool>);

#[component]
pub fn App() -> impl IntoView {
    let (red, set_red) = create_signal(false);
    view! {
        <ContextProvider value=SmallcapsContext(set_red)>
            <main>
                <App2/>
                <div>
                    <p class:red=red>"apple"</p>
                    <Button/>
                </div>
            </main>
        </ContextProvider>
    }
}

#[component]
pub fn Button() -> impl IntoView {
    let setter = use_context::<SmallcapsContext>().unwrap().0;

    view! {
        <button on:click=move |_| {
            setter.update(|value| *value = !*value)
        }>"Toggle Red"</button>
    }
}

#[component]
fn App2() -> impl IntoView {
    let (red, set_red) = create_signal(false);

    view! {
        <ContextProvider value=SmallcapsContext(set_red)>
            <main>
                <p class:red=red>
                    "apple2"
                </p>
                <Button2/>
            </main>
        </ContextProvider>
    }
}

#[component]
pub fn Button2() -> impl IntoView {
    let setter = use_context::<SmallcapsContext>().unwrap().0;

    view! {
        <button on:click=move |_| {
            setter.update(|value| *value = !*value)
        }>"2. Toggle Red"</button>
    }
}

@luoxiaozero
Copy link
Contributor Author

luoxiaozero commented Nov 18, 2023

It looks good. This is a problem I found when writing the component library. I tried it out with the ContextProvider you provided and it worked fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants