Often times, you'll want to wrap some important functionality around your state, not directly nested inside another component. In these cases, you'll want to pass elements and attributes into a component and let the component place them appropriately.
In this chapter, you'll learn about:
Let's say you're building a user interface and want to make some part of it clickable to another website. You would normally start with the HTML <a>
tag, like so:
rsx!(
a {
href: "https://google.com"
"Link to google"
}
)
But, what if we wanted to style our <a>
tag? Or wrap it with some helper icon? We could abstract our RSX into its own component:
#[derive(Props)]
struct ClickableProps<'a> {
href: &'a str,
title: &'a str
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
"{cx.props.title}"
}
))
}
And then use it in our code like so:
rsx!(
clickable(
href: "https://google.com"
title: "Link to Google"
)
)
Let's say we don't just want the text to be clickable, but we want another element, like an image, to be clickable. How do we implement that?
If we want to pass an image into our component, we can just adjust our props and component to allow any Element
.
#[derive(Props)]
struct ClickableProps<'a> {
href: &'a str,
body: Element<'a>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
{&cx.props.body}
}
))
}
Then, at the call site, we can render some nodes and pass them in:
rsx!(
clickable(
href: "https://google.com"
body: cx.render(rsx!(
img { src: "https://www.google.com/logos/doodles/2021/seasonal-holidays-2021-6753651837109324-6752733080595603-cst.gif" }
))
)
)
This pattern can become tedious in some instances, so Dioxus actually performs an implicit conversion of any rsx
calls inside components into Elements
at the children
field. This means you must explicitly declare if a component can take children.
#[derive(Props)]
struct ClickableProps<'a> {
href: &'a str,
children: Element<'a>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
{&cx.props.children}
}
))
}
And to call clickable
:
rsx!(
clickable(
href: "https://google.com"
img { src: "https://www.google.com/logos/doodles/2021/seasonal-holidays-2021-6753651837109324-6752733080595603-cst.gif" }
)
)
Note: Passing children into components will break any memoization due to the associated lifetime.
While technically allowed, it's an antipattern to pass children more than once in a component and will probably break your app significantly.
However, because the Element
is transparently a VNode
, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format:
fn clickable(cx: Scope<ClickableProps>) -> Element {
match cx.props.children {
Some(VNode::Text(text)) => {
// ...
}
_ => {
// ...
}
}
}
In the cases where you need to pass arbitrary element properties into a component - say to add more functionality to the <a>
tag, Dioxus will accept any quoted fields. This is similar to adding arbitrary fields to regular elements using quotes.
rsx!(
clickable(
"class": "blue-button",
"style": "background: red;"
)
)
For a component to accept these attributes, you must add an attributes
field to your component's properties. We can use the spread syntax to add these attributes to whatever nodes are in our component.
#[derive(Props)]
struct ClickableProps<'a> {
attributes: Attributes<'a>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
..{cx.props.attributes},
"Any link, anywhere"
}
))
}
The quoted escapes are a great way to make your components more flexible.
Dioxus also provides some implicit conversions from listener attributes into an EventHandler
for any field on components that starts with on
. IE onclick
, onhover
, etc. For properties, we want to define our on
fields as an event handler:
#[derive(Props)]
struct ClickableProps<'a> {
onclick: EventHandler<'a, MouseEvent>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
onclick: move |evt| cx.props.onclick.call(evt)
}
))
}
Then, we can attach a listener at the call site:
rsx!(
clickable(
onclick: move |_| log::info!("Clicked"),
)
)
Currently, Dioxus does not support an arbitrary amount of listeners - they must be strongly typed in Properties
. If you need this use case, you can pass in an element with these listeners, or dip down into the NodeFactory
API.
In this chapter, we learned:
children
field works on component propertiesattributes
field works on component propertieslisteners
into EventHandlers
for componentsNext chapter, we'll talk about conditionally rendering parts of your user interface.