Skip to content

Commit 1e5344b

Browse files
committed
feat: add IntoPropValue<ChildrenRenderer<VNode>> for string-like types
String, &AttrValue, Rc<str>, and Cow<'static, str> can now be used directly as children of components with `children: Children` props, without requiring an explicit `.into()` call.
1 parent 83c78a4 commit 1e5344b

5 files changed

Lines changed: 239 additions & 6 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
2+
pub struct ContainerProps {
3+
#[prop_or_default]
4+
pub children: ::yew::Html,
5+
}
6+
7+
#[::yew::component]
8+
fn Container(_props: &ContainerProps) -> ::yew::Html {
9+
::yew::html! {}
10+
}
11+
12+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
13+
pub struct AccordionProps {
14+
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
15+
}
16+
17+
#[::yew::component]
18+
fn Accordion(props: &AccordionProps) -> ::yew::Html {
19+
::yew::html! {
20+
<div>
21+
{ for props.children.iter().map(|item| ::yew::html!(
22+
<div class="item">
23+
{ item }
24+
</div>
25+
)) }
26+
</div>
27+
}
28+
}
29+
30+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
31+
pub struct PanelProps {
32+
#[prop_or_default]
33+
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
34+
}
35+
36+
#[::yew::component]
37+
fn Panel(props: &PanelProps) -> ::yew::Html {
38+
if props.children.is_empty() {
39+
::yew::html! { <div>{"no children"}</div> }
40+
} else {
41+
::yew::html! { <div>{ for props.children.iter() }</div> }
42+
}
43+
}
44+
45+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
46+
pub struct LabelProps {
47+
pub content: ::yew::Html,
48+
}
49+
50+
#[::yew::component]
51+
fn Label(_props: &LabelProps) -> ::yew::Html {
52+
::yew::html! {}
53+
}
54+
55+
fn main() {
56+
let _ = ::yew::html! {
57+
<Container>
58+
<div>{"hello"}</div>
59+
<span>{"world"}</span>
60+
</Container>
61+
};
62+
63+
let _ = ::yew::html! {
64+
<Container>{"just text"}</Container>
65+
};
66+
67+
let _ = ::yew::html! { <Container /> };
68+
69+
let s = ::std::string::String::from("hello");
70+
let _ = ::yew::html! { <Label content={s} /> };
71+
72+
let _ = ::yew::html! { <Label content={"world"} /> };
73+
74+
let label = ::std::string::String::from("hello");
75+
let _ = ::yew::html! { <div>{&label}</div> };
76+
let _ = ::yew::html! { <div>{label}</div> };
77+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Custom types with From<T> for VNode or Display do not automatically work
2+
// in html! blocks, because blocks require IntoPropValue<VNode>, not Into<VNode>.
3+
//
4+
// A blanket `impl<T: Into<VNode>> IntoPropValue<VNode> for T` would fix this
5+
// but conflicts with the identity impl `impl<T> IntoPropValue<T> for T` when
6+
// T = VNode. Resolving this overlap requires the `specialization` feature
7+
// (rust-lang/rust#31844), which is still unstable.
8+
//
9+
// Workaround: call `.into()` explicitly or implement IntoPropValue<VNode>.
10+
11+
struct Print {
12+
text: ::std::string::String,
13+
}
14+
15+
impl ::std::convert::From<Print> for ::yew::virtual_dom::VNode {
16+
fn from(val: Print) -> Self {
17+
::yew::html! {<>{"Hello "}{val.text}</>}
18+
}
19+
}
20+
21+
struct Ratio(::std::primitive::f64);
22+
23+
impl ::std::fmt::Display for Ratio {
24+
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
25+
::std::write!(f, "{:.2}", self.0)
26+
}
27+
}
28+
29+
enum Label {
30+
Text(::std::string::String),
31+
Number(::std::primitive::i32),
32+
}
33+
34+
impl ::std::convert::From<Label> for ::yew::virtual_dom::VNode {
35+
fn from(val: Label) -> Self {
36+
match val {
37+
Label::Text(t) => ::yew::html! { <span>{t}</span> },
38+
Label::Number(n) => ::yew::html! { <span>{n}</span> },
39+
}
40+
}
41+
}
42+
43+
fn main() {
44+
let text = Print {
45+
text: "World".into(),
46+
};
47+
let _ = ::yew::html! { <div>{text}</div> };
48+
49+
let setback = Ratio(3.14);
50+
let _ = ::yew::html! { <div>{setback}</div> };
51+
52+
let label = Label::Text("hello".into());
53+
let _ = ::yew::html! { <div>{label}</div> };
54+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
error[E0277]: the trait bound `Print: IntoPropValue<VNode>` is not satisfied
2+
--> tests/html_macro/custom-type-in-block-fail.rs:47:34
3+
|
4+
47 | let _ = ::yew::html! { <div>{text}</div> };
5+
| ---------------------^^^^---------
6+
| | |
7+
| | the trait `IntoPropValue<VNode>` is not implemented for `Print`
8+
| required by a bound introduced by this call
9+
|
10+
= help: the following other types implement trait `IntoPropValue<T>`:
11+
`&&String` implements `IntoPropValue<Option<VNode>>`
12+
`&&String` implements `IntoPropValue<VNode>`
13+
`&&str` implements `IntoPropValue<Option<VNode>>`
14+
`&&str` implements `IntoPropValue<VNode>`
15+
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
16+
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
17+
`&'static str` implements `IntoPropValue<Classes>`
18+
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
19+
and $N others
20+
21+
error[E0277]: the trait bound `Ratio: IntoPropValue<VNode>` is not satisfied
22+
--> tests/html_macro/custom-type-in-block-fail.rs:50:34
23+
|
24+
50 | let _ = ::yew::html! { <div>{setback}</div> };
25+
| ---------------------^^^^^^^---------
26+
| | |
27+
| | the trait `IntoPropValue<VNode>` is not implemented for `Ratio`
28+
| required by a bound introduced by this call
29+
|
30+
= help: the following other types implement trait `IntoPropValue<T>`:
31+
`&&String` implements `IntoPropValue<Option<VNode>>`
32+
`&&String` implements `IntoPropValue<VNode>`
33+
`&&str` implements `IntoPropValue<Option<VNode>>`
34+
`&&str` implements `IntoPropValue<VNode>`
35+
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
36+
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
37+
`&'static str` implements `IntoPropValue<Classes>`
38+
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
39+
and $N others
40+
41+
error[E0277]: the trait bound `Label: IntoPropValue<VNode>` is not satisfied
42+
--> tests/html_macro/custom-type-in-block-fail.rs:53:34
43+
|
44+
53 | let _ = ::yew::html! { <div>{label}</div> };
45+
| ---------------------^^^^^---------
46+
| | |
47+
| | the trait `IntoPropValue<VNode>` is not implemented for `Label`
48+
| required by a bound introduced by this call
49+
|
50+
= help: the following other types implement trait `IntoPropValue<T>`:
51+
`&&String` implements `IntoPropValue<Option<VNode>>`
52+
`&&String` implements `IntoPropValue<VNode>`
53+
`&&str` implements `IntoPropValue<Option<VNode>>`
54+
`&&str` implements `IntoPropValue<VNode>`
55+
`&'static [(K, V)]` implements `IntoPropValue<implicit_clone::unsync::map::IMap<K, V>>`
56+
`&'static [T]` implements `IntoPropValue<implicit_clone::unsync::array::IArray<T>>`
57+
`&'static str` implements `IntoPropValue<Classes>`
58+
`&'static str` implements `IntoPropValue<Cow<'static, str>>`
59+
and $N others
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
2+
pub struct TextProps {
3+
pub children: ::yew::html::ChildrenRenderer<::yew::virtual_dom::VNode>,
4+
}
5+
6+
#[::yew::component]
7+
fn Text(_props: &TextProps) -> ::yew::Html {
8+
::yew::html! {}
9+
}
10+
11+
#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)]
12+
pub struct ExampleProps {
13+
pub text: ::yew::AttrValue,
14+
}
15+
16+
#[::yew::component]
17+
fn Example(props: &ExampleProps) -> ::yew::Html {
18+
::yew::html! {
19+
<Text>
20+
{&props.text}
21+
</Text>
22+
}
23+
}
24+
25+
fn main() {
26+
let _ = ::yew::html! { <Text>{"hello"}</Text> };
27+
28+
let s = ::std::string::String::from("world");
29+
let _ = ::yew::html! { <Text>{s}</Text> };
30+
31+
let _ = ::yew::html! { <Text>{ ::std::format!("year {}", 2024) }</Text> };
32+
33+
let status: ::std::option::Option<::std::string::String> =
34+
::std::option::Option::Some("Active".into());
35+
let _ = ::yew::html! {
36+
<Text>
37+
{ status.as_ref().map_or_else(
38+
::std::string::String::new,
39+
|s| ::std::format!("Status: {}", s)
40+
) }
41+
</Text>
42+
};
43+
}

packages/yew/src/html/conversion/into_prop_value.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,6 @@ impl<C: BaseComponent> IntoPropValue<VList> for VChild<C> {
201201
}
202202
}
203203

204-
impl IntoPropValue<ChildrenRenderer<VNode>> for AttrValue {
205-
fn into_prop_value(self) -> ChildrenRenderer<VNode> {
206-
ChildrenRenderer::new(vec![VNode::VText(VText::new(self))])
207-
}
208-
}
209-
210204
impl IntoPropValue<VNode> for Vec<VNode> {
211205
#[inline]
212206
fn into_prop_value(self) -> VNode {
@@ -343,6 +337,12 @@ macro_rules! impl_into_prop_value_via_attr_value {
343337
self.map(|v| VText::new(v).into())
344338
}
345339
}
340+
impl IntoPropValue<ChildrenRenderer<VNode>> for $from_ty {
341+
#[inline(always)]
342+
fn into_prop_value(self) -> ChildrenRenderer<VNode> {
343+
ChildrenRenderer::new(vec![VText::new(self).into()])
344+
}
345+
}
346346
};
347347
}
348348

0 commit comments

Comments
 (0)