ChildrenWithProps<> doesn't implement iter_mut()
Problem
props.children doesn't implement iter_mut(). This makes it very difficult to iterate over the children and make a change on all of them (say, for example, set a value in the child props to false).
Steps To Reproduce Steps to reproduce the behavior:
pub struct GroupProps {
pub children: ChildrenWithProps<Item>,
}
impl Component for Item {
fn update(&mut self, msg: Self::Message) -> bool {
self.props.children.iter_mut().for_each(|child| {
child.is_selected = false;
});
}
}
Expected behavior
props.children should implement iter_mut(), allowing us to iterate over the children and mutate them.
Screenshots None
Environment:
- Yew version: v0.17
- Rust version: 1.51.0
- Target, if relevant:
wasm32-unknown-unknown. - Build tool, if relevant: -
- OS, if relevant: -
- Browser and version, if relevant: -
Questionnaire
- [x] I'm interested in fixing this myself but don't know where to start
- [ ] I would like to fix and I have a solution
- [ ] I don't have time to fix this right now, but maybe later
You're probably using ChildrenWithProps::<_>::iter(); wrong. It doesn't allow mutation because you're supposed to mutate the elements, then render them to the VDom using Component::view();. See the example on the docs page
#[derive(Clone, Properties)]
struct ListProps {
children: ChildrenWithProps<ListItem>,
}
impl Component for List {
// ...
fn view(&self) -> Html {
html!{{
for self.props.children.iter().map(|mut item| {
item.props.value = format!("item-{}", item.props.value);
item
})
}}
}
}
I guess making that a feature of ChildrenWithProps::<_> is what you're asking, so that you can do mutations in other component functions, but I'm not sure if using it as state is a good idea. Just adds another opening for a bug to creep in.
I see your point of view, but what I'm trying to do is to make a RadioGroup component which hosts inner RadioButton components. Now what I want to do is to make a private on_change_listener inside the RadioButton which notifies the parent RadioGroup that a radio button has been clicked, so that it can disable the rest of the buttons in the same group. So when I detect a change event or in the create event, I'm assigning the on_change_listener to the parent to set the change listener. So this requires me to iterate over the (mutable) children as assign the listener to each of them. I'm open to suggestions on how to go about this.
I actually made something like what you want to do yesterday. Here it is
The solution was to simply not use children and use a map. I wrote my own map that statically maps enums to an array, which has insanely low overhead, but using something like a HashMap would still have less overhead, since Yew does a lot of work for your component to participate in the DOM.
So you're saying we should create a new component by mapping the existing one in the render call? Isn't that expensive?
So you're saying we should create a new component by mapping the existing one in the render call? Isn't that expensive?
I'm saying use a list of states rather than a list of components, and then render those states to the DOM.
So take a prop of a bunch of RadioButtons and render the buttons accordingly?
I was thinking it'll make sense to have a RadioButton so that it can be used separately if we have to, and have it as the children of the RadioGroup to make it work together.
Also, apparently ChildrenRenderer<T> is implemented as a Vec<T> internally, so it should be pretty simple to implement iter_mut by simply calling the function on the Vec
So take a prop of a bunch of
RadioButtons and render the buttons accordingly?
I guess so. In Yew, I'd probably instead mark the indexes for each RadioButton component, something along the lines of this:
pub struct RadioGroup {
children: ChildrenWithProps<RadioButton>,
selected: usize,
}
impl Component for RadioGroup {
// ...
fn update(&mut self, msg: Msg) -> ShouldRender {
match msg {
// ...
Msg::Update(selected) => {
self.selected = selected;
}
}
}
fn view(&self) -> Html {
html! {
<div>{
// now we assign an index to each radio button:
for self.children.iter().enumerate().map(|(i, radio)| {
radio.props.onselect = self.link.callback(|_| Msg::Update(i));
radio.props.selected = self.selected == i;
radio
})
}</div>
}
}
}
Hi 👋
The state of things in Yew has changed since this issue was first raised. Properties are now stored in Rcs so are effectively readonly, so this is no longer as easy to implement. #1961 is the PR that introduced this change. In order to implement this solution now we'd need to introduce interior mutability for Context<Self> (bit like ComponentLink<Self> but holds Properties too and lives outside of the component state).
Let me know if you still want to request this feature or if the approach provided by @frostu8 (thank you btw ❤️) solved the need for the feature.
Housekeeping:
I guess making that a feature of ChildrenWithProps::<_> is what you're asking
Agreed, I have swapped the bug label for the feature request label :)