dioxus/packages/core-macro/docs/props.md
Evan Almloff 3a4860add4
Document props and component macro (#2522)
* document prop attributes

* document children props

* fix children doc test
2024-06-18 16:06:54 -07:00

9.7 KiB

Props

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.

Example

# 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",
    }
};

Prop Modifiers

You can use the #[props()] attribute to modify the behavior of the props derive macro:

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.

Default Props

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 {}
};

Optional Props

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
    }
};

Converting Props

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
    }
};

Formatted Props

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}!"
    }
};

Children Props

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"
    }
};

Reactive Props

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:

  1. Make the prop reactive by wrapping it in 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}"
        }
    }
}
  1. 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}"
        }
    }
}

Extending Elements

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",
    }
};