-
I am not sure whether this is a bug or a feature. Anyway, I need to find a workaround for this. Let's assume the following example: use leptos::*;
#[component]
fn App(cx: Scope) -> impl IntoView {
provide_context(cx, 10_usize);
view! { cx,
<List>
<ListItem>"ten"</ListItem>
<ListItem>"eleven"</ListItem>
<ListItem>"twelve"</ListItem>
</List>
}
}
#[component]
fn List(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
let children = children(cx)
.nodes
.into_iter()
.enumerate()
.map(|(i, child)| {
let value = context + i;
view! { cx, <ContextProvider context=value>{child}</ContextProvider> }
})
.collect_view(cx);
view! { cx, <ul>{children}</ul> }
}
#[component]
fn ListItem(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
view! { cx, <li>{children(cx)} " -> " {context}</li> }
}
#[component]
fn ContextProvider<T>(cx: Scope, context: T, children: Children) -> impl IntoView
where
T: Clone + 'static,
{
provide_context(cx, context);
children(cx)
}
fn main() {
mount_to_body(App);
} Expected result:
Result:
Is there a way to override context within the |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
If you add a couple logs, you'll see more about the order your components run in, which will help. #[component]
fn ListItem(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
log!("using context in {cx:?}");
view! { cx, <li>{children(cx)} " -> " {context}</li> }
}
#[component]
fn ContextProvider<T>(cx: Scope, context: T, children: Children) -> impl IntoView
where
T: Clone + Debug + 'static,
{
log!("providing {context:?} on {cx:?}");
provide_context(cx, context);
children(cx)
} yields
This makes sense: you construct the children, after all, here, as children of the let children = children(cx) then you project the already-created child node into the children of view! { cx, <ContextProvider context=value>{child}</ContextProvider> } I guess a way to do this would be not to consume the context until you've provided it via the context provider. Here would be an example of how to achieve that without changing the API/how you're using it in #[component]
fn List(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
let children = children(cx)
.nodes
.into_iter()
.enumerate()
.map(|(i, child)| {
let value = context + i;
view! { cx, <ContextProvider context=value>
<ListItemInner>{child}</ListItemInner>
</ContextProvider> }
})
.collect_view(cx);
view! { cx, <ul>{children}</ul> }
}
#[component]
fn ListItem(cx: Scope, children: Children) -> impl IntoView {
children(cx)
}
#[component]
fn ListItemInner(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
view! { cx, <li>{children(cx)} " -> " {context}</li> }
} |
Beta Was this translation helpful? Give feedback.
-
Ah, okay. So, let me take another try at this that doesn't cheat by introducing a new component, and might be closer to what you need. For what it's worth: I think the core issue here is that without the blessing of a virtual DOM (and the curse of its overhead), and because components don't re-run when state changes, Leptos tends to construct things eagerly: in this case, So the model of wrapping components in context providers is never quite going to work exactly the same, because the control flow is just different enough between the two frameworks. However, we can still do some fun and cool stuff with context to make this work. We just need to make sure the context is provided by Not having looked at Lerni in depth, I'm not sure how well this solves the context problem you need to solve. But to the extent that it solves the problem as originally formulated, without needing to introduce additional wrapper components, and supports multiple different types of child component (see the #[component]
pub fn App(cx: Scope) -> impl IntoView {
provide_context(cx, 10_usize);
view! { cx,
<List>
<ListItem>"ten"</ListItem>
<ListItem>"eleven"</ListItem>
<ListItem>"twelve"</ListItem>
<Label>"thirteen"</Label>
</List>
}
}
fn provide_index(cx: Scope, initial: usize) {
provide_context(cx, Rc::new(Cell::new(initial)));
}
fn use_index(cx: Scope) -> usize {
let stored = expect_context::<Rc<Cell<usize>>>(cx);
let current = stored.get();
stored.set(current + 1);
current
}
#[component]
fn List(cx: Scope, children: Children) -> impl IntoView {
let context = use_context::<usize>(cx).unwrap();
provide_index(cx, context);
view! { cx, <ul>{children(cx)}</ul> }
}
#[component]
fn ListItem(cx: Scope, children: Children) -> impl IntoView {
let index = use_index(cx);
view! { cx, <li>"List Item " {children(cx)} " -> " {index}</li> }
}
#[component]
fn Label(cx: Scope, children: Children) -> impl IntoView {
let index = use_index(cx);
view! { cx, <li>"Label " {children(cx)} " -> " {index}</li> }
} |
Beta Was this translation helpful? Give feedback.
Ah, okay. So, let me take another try at this that doesn't cheat by introducing a new component, and might be closer to what you need.
For what it's worth: I think the core issue here is that without the blessing of a virtual DOM (and the curse of its overhead), and because components don't re-run when state changes, Leptos tends to construct things eagerly: in this case,
children
is lazy, but each individual child node is not, so as soon as you callchildren(cx)
, all the children are built.So the model of wrapping components in context providers is never quite going to work exactly the same, because the control flow is just different enough between the two frameworks.
However, we can st…