The props derive macro allows you to define what props your component accepts and how to accept those props. Every component must either accept no arguments or accept a single argument that implements the Properties
trait.
Note: You should generally prefer using the
#[component]
macro instead of the#[derive(Props)]
macro with explicit props. The#[component]
macro will automatically generate the props struct for you and perform extra checks to ensure that your component is valid.
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
/// The text of the button
text: String,
/// The color of the button
color: String,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button {
color: props.color,
"{props.text}"
}
}
}
rsx! {
// Any fields you defined on the props struct will be turned into props for the component.
Button {
text: "Click me!",
color: "red",
}
};
You can use the #[props()]
attribute to modify the behavior of the props derive macro:
#[props(default)]
- Makes the field optional in the component and uses the default value if it is not set when creating the component.#[props(!optional)]
- Makes a field with the type Option<T>
required.#[props(into)]
- Converts a field into the correct type by using the [Into
] trait.#[props(extends = GlobalAttributes)]
- Extends the props with all the attributes from an element or the global element attributes.Props also act slightly differently when used with:
Option<T>
- The field is automatically optional with a default value of None
.ReadOnlySignal<T>
- The props macro will automatically convert T
into ReadOnlySignal<T>
when it is passed as a prop.String
- The props macro will accept formatted strings for any prop field with the type String
.children
- The props macro will accept child elements if you include the children
prop.The default
attribute lets you define a default value for a field if it isn't set when creating the component
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
// The default attributes makes your field optional in the component and uses the default value if it is not set.
#[props(default)]
text: String,
/// You can also set an explicit default value instead of using the `Default` implementation.
#[props(default = "red".to_string())]
color: String,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button {
color: props.color,
"{props.text}"
}
}
}
rsx! {
// You can skip setting props that have a default value when you use the component.
Button {}
};
When defining props, you may want to make a prop optional without defining an explicit default value. Any fields with the type Option<T>
are automatically optional with a default value of None
.
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
// Since the `text` field is optional, you don't need to set it when you use the component.
text: Option<String>,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button { {props.text.unwrap_or("button".to_string())} }
}
}
rsx! {
Button {}
};
If you want to make your Option<T>
field required, you can use the !optional
attribute:
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
/// You can use the `!optional` attribute on a field with the type `Option<T>` to make it required.
#[props(!optional)]
text: Option<String>,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button { {props.text.unwrap_or("button".to_string())} }
}
}
rsx! {
Button {
text: None
}
};
You can automatically convert a field into the correct type by using the into
attribute. Any type you pass into the field will be converted with the [Into
] trait:
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
/// You can use the `into` attribute on a field to convert types you pass in with the Into trait.
#[props(into)]
number: u64,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button { "{props.number}" }
}
}
rsx! {
Button {
// Because we used the into attribute, we can pass in any type that implements Into<u64>
number: 10u8
}
};
You can use formatted strings in attributes just like you would in an element. Any prop field with the type String
can accept a formatted string:
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
text: String,
}
fn Button(props: ButtonProps) -> Element {
rsx! {
button { "{props.text}" }
}
}
let name = "Bob";
rsx! {
Button {
// You can use formatted strings in props that accept String just like you would in an element.
text: "Hello {name}!"
}
};
Rather than passing the RSX through a regular prop, you may wish to accept children similarly to how elements can have children. The "magic" children prop lets you achieve this:
# use dioxus::prelude::*;
#[derive(PartialEq, Clone, Props)]
struct ClickableProps {
href: String,
children: Element,
}
fn Clickable(props: ClickableProps) -> Element {
rsx! {
a {
href: "{props.href}",
class: "fancy-button",
{props.children}
}
}
}
This makes providing children to the component much simpler: simply put the RSX inside the {} brackets:
# use dioxus::prelude::*;
# #[derive(PartialEq, Clone, Props)]
# struct ClickableProps {
# href: String,
# children: Element,
# }
#
# fn Clickable(props: ClickableProps) -> Element {
# rsx! {
# a {
# href: "{props.href}",
# class: "fancy-button",
# {props.children}
# }
# }
# }
rsx! {
Clickable {
href: "https://www.youtube.com/watch?v=C-M2hs3sXGo",
"How to "
i { "not" }
" be seen"
}
};
In dioxus, when a prop changes, the component will rerun with the new value to update the UI. For example, if count changes from 0 to 1, this component will rerun and update the UI to show "Count: 1":
# use dioxus::prelude::*;
#[component]
fn Counter(count: i32) -> Element {
rsx! {
div {
"Count: {count}"
}
}
}
Generally, just rerunning the component is enough to update the UI. However, if you use your prop inside reactive hooks like use_memo
or use_resource
, you may also want to restart those hooks when the prop changes:
# use dioxus::prelude::*;
#[component]
fn Counter(count: i32) -> Element {
// We can use a memo to calculate the doubled count. Since this memo will only be created the first time the component is run and `count` is not reactive, it will never update when `count` changes.
let doubled_count = use_memo(move || count * 2);
rsx! {
div {
"Count: {count}"
"Doubled Count: {doubled_count}"
}
}
}
To fix this issue you can either:
ReadOnlySignal
(recommended):ReadOnlySignal
is a Copy
reactive value. Dioxus will automatically convert any value into a ReadOnlySignal
when it is passed as a prop.
# use dioxus::prelude::*;
#[component]
fn Counter(count: ReadOnlySignal<i32>) -> Element {
// Since we made count reactive, the memo will automatically rerun when count changes.
let doubled_count = use_memo(move || count() * 2);
rsx! {
div {
"Count: {count}"
"Doubled Count: {doubled_count}"
}
}
}
Explicitly add the prop as a dependency to the reactive hook with use_reactive
:
# use dioxus::prelude::*;
#[component]
fn Counter(count: i32) -> Element {
// We can add the count prop as an explicit dependency to every reactive hook that uses it with use_reactive.
// The use_reactive macro takes a closure with explicit dependencies as its argument.
let doubled_count = use_memo(use_reactive!(|count| count * 2));
rsx! {
div {
"Count: {count}"
"Doubled Count: {doubled_count}"
}
}
}
The extends
attribute lets you extend your props with all the attributes from an element or the global element attributes.
# use dioxus::prelude::*;
#[derive(Props, PartialEq, Clone)]
struct ButtonProps {
/// You can use the `extends` attribute on a field with the type `Vec<Attribute>` to extend the props with all the attributes from an element or the global element attributes.
#[props(extends = GlobalAttributes)]
attributes: Vec<Attribute>,
}
#[component]
fn Button(props: ButtonProps) -> Element {
rsx! {
// Instead of copying over every single attribute, we can just spread the attributes from the props into the button.
button { ..props.attributes, "button" }
}
}
rsx! {
// Since we extend global attributes, you can use any attribute that would normally appear on the button element.
Button {
width: "10px",
height: "10px",
color: "red",
}
};