wip: remove runner on hook and then update docs
This commit is contained in:
1 parent
7d41a16208
commit
d1560450ba
95 files changed
+1339
-675
No files matched your search
@@ -0,0 +1,219 @@
|
||||
# Passing children and attributes
|
||||
|
||||
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:
|
||||
- Passing elements into components
|
||||
- Passing attributes into components
|
||||
|
||||
|
||||
## The use case
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
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:
|
||||
|
||||
|
||||
```rust
|
||||
#[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:
|
||||
|
||||
```rust
|
||||
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?
|
||||
|
||||
## Passing children
|
||||
|
||||
If we want to pass an image into our component, we can just adjust our props and component to allow any `Element`.
|
||||
|
||||
```rust
|
||||
#[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:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
Clickable {
|
||||
href: "https://google.com"
|
||||
body: cx.render(rsx!(
|
||||
img { src: "https://www.google.com/logos/doodles/..." }
|
||||
))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Auto Conversion of the `Children` field
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
#[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
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
Now, whenever we use `Clickable` in another component, we don't need to call `render` on child nodes - it will happen automatically!
|
||||
```rust
|
||||
rsx!(
|
||||
Clickable {
|
||||
href: "https://google.com"
|
||||
img { src: "https://www.google.com/logos/doodles/...." }
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
> 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 cause your app to crash.
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
fn clickable(cx: Scope<ClickableProps>) -> Element {
|
||||
match cx.props.children {
|
||||
Some(VNode::Text(text)) => {
|
||||
// ...
|
||||
}
|
||||
_ => {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Passing attributes
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
#[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.
|
||||
|
||||
|
||||
## Passing handlers
|
||||
|
||||
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:
|
||||
|
||||
|
||||
```rust
|
||||
#[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:
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we learned:
|
||||
- How to pass arbitrary nodes through the tree
|
||||
- How the `children` field works on component properties
|
||||
- How the `attributes` field works on component properties
|
||||
- How to convert `listeners` into `EventHandlers` for components
|
||||
- How to extend any node with custom attributes and children
|
||||
|
||||
Next chapter, we'll talk about conditionally rendering parts of your user interface.
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# Introduction to Components
|
||||
|
||||
In the previous chapter, we learned about Elements and how they can be composed to create a basic User Interface. In this chapter, we'll learn how to group Elements together to form Components.
|
||||
|
||||
In this chapter, we'll learn:
|
||||
- What makes a Component
|
||||
- How to model a component and its properties in Dioxus
|
||||
- How to "think declaratively"
|
||||
|
||||
## What is a component?
|
||||
|
||||
In short, a component is a special function that takes input properties and outputs an Element. Typically, Components serve a single purpose: group functionality of a User Interface. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task.
|
||||
|
||||
### Learning through prior art
|
||||
|
||||
Let's take a look at a post on r/rust and see if we can sketch out a component representation.
|
||||
|
||||

|
||||
|
||||
This component has a bunch of important information:
|
||||
|
||||
- The score
|
||||
- The number of comments
|
||||
- How long ago it was posted
|
||||
- The url short address
|
||||
- The title
|
||||
- The username of the original poster
|
||||
|
||||
If we wanted to sketch out these requirements in Rust, we would start with a struct:
|
||||
|
||||
```rust
|
||||
struct PostData {
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster_name: String
|
||||
}
|
||||
```
|
||||
|
||||
If we look at the layout of the component, we notice quite a few buttons and functionality:
|
||||
|
||||
- Upvote/Downvote
|
||||
- View comments
|
||||
- Share
|
||||
- Save
|
||||
- Hide
|
||||
- Give award
|
||||
- Report
|
||||
- Crosspost
|
||||
- Filter by site
|
||||
- View article
|
||||
- Visit user
|
||||
|
||||
If we included all this functionality in one `rsx!` call, it would be huge! Instead, let's break the post down into some core pieces:
|
||||
|
||||

|
||||
|
||||
- **VoteButton**: Upvote/Downvote
|
||||
- **TitleCard**: Title, Filter-By-Url
|
||||
- **MetaCard**: Original Poster, Time Submitted
|
||||
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
|
||||
|
||||
### Modeling with Dioxus
|
||||
|
||||
We can start by sketching out the Element hierarchy using Dioxus. In general, our "Post" component will be comprised of the four sub-components listed above. First, let's define our `Post` component.
|
||||
|
||||
Unlike normal functions, Dioxus components must explicitly define a single struct to contain all the inputs. These are commonly called "Properties" (props). Our component will be a combination of these properties and a function to render them.
|
||||
|
||||
Our props must implement the `Props` trait and - if the component does not borrow any data - `PartialEq`. Both of these can be done automatically through derive macros:
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostProps {
|
||||
id: Uuid,
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster: String
|
||||
}
|
||||
```
|
||||
|
||||
And our render function:
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
VoteButton {
|
||||
score: cx.props.score,
|
||||
}
|
||||
TitleCard {
|
||||
title: cx.props.title,
|
||||
url: cx.props.url,
|
||||
}
|
||||
MetaCard {
|
||||
original_poster: cx.props.original_poster,
|
||||
post_time: cx.props.post_time,
|
||||
}
|
||||
ActionCard {
|
||||
post_id: cx.props.id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
When declaring a component in `rsx!`, we can pass in properties using the traditional Rust struct syntax. Dioxus will automatically call "into" on the property fields, cloning when necessary. Our `Post` component is simply a collection of smaller components wrapped together in a single container.
|
||||
|
||||
Let's take a look at the `VoteButton` component. For now, we won't include any interactivity - just the rendering the score and buttons to the screen.
|
||||
|
||||
Most of your Components will look exactly like this: a Props struct and a render function. Every component must take a `Scope` generic over some `Props` and return an `Element`.
|
||||
|
||||
As covered before, we'll build our User Interface with the `rsx!` macro and HTML tags. However, with components, we must actually "render" our HTML markup. Calling `cx.render` converts our "lazy" `rsx!` structure into an `Element`.
|
||||
|
||||
```rust
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton(cx: Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "votebutton"
|
||||
div { class: "arrow up" }
|
||||
div { class: "score", "{cx.props.score}"}
|
||||
div { class: "arrow down" }
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Borrowing
|
||||
|
||||
You can avoid clones using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
|
||||
|
||||
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
|
||||
|
||||
To enable borrowed values for your component, we need to add a lifetime to let the Rust compiler know that the output `Element` borrows from the component's props.
|
||||
|
||||
```rust
|
||||
#[derive(Props)]
|
||||
struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For users of React: Dioxus knows *not* to memoize components that borrow property fields. By default, every component in Dioxus is memoized. This can be disabled by the presence of a non-`'static` borrow.
|
||||
|
||||
This means that during the render process, a newer version of `TitleCardProps` will never be compared with a previous version, saving some clock cycles.
|
||||
|
||||
## The inline_props macro
|
||||
|
||||
Yes - *another* macro! However, this one is entirely optional.
|
||||
|
||||
For internal components, we provide the `inline_props` macro, which will let you embed your `Props` definition right into the function arguments of your component.
|
||||
|
||||
Our title card above would be transformed from:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct TitleCardProps {
|
||||
title: String,
|
||||
}
|
||||
|
||||
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{cx.props.title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```rust
|
||||
#[inline_props]
|
||||
fn TitleCard(cx: Scope, title: String) -> Element {
|
||||
cx.render(rsx!{
|
||||
h1 { "{title}" }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Again, this macro is optional and should not be used by library authors since you have less fine-grained control over documentation and optionality.
|
||||
|
||||
However, it's great for quickly throwing together an app without dealing with *any* extra boilerplate.
|
||||
|
||||
## The `Scope` object
|
||||
|
||||
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.
|
||||
|
||||
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
|
||||
|
||||
React uses global variables to store this information. Global mutable variables must be carefully managed and are broadly discouraged in Rust programs.
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let [state, set_state] = useState(10);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Because Dioxus needs to work with the rules of Rust it uses the `Scope` object to maintain some internal bookkeeping. That's what the `Scope` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Scope` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post(cx: Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!("hello"))
|
||||
}
|
||||
```
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll talk about composing Elements and Components across files to build a larger Dioxus App.
|
||||
|
||||
For more references on components, make sure to check out:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Scope object]()
|
||||
- [Optional Prop fields]()
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# Conditional Rendering
|
||||
|
||||
Your components will often need to display different things depending on different conditions. With Dioxus, we can use Rust's normal control flow to conditional hide, show, and modify the structure of our markup.
|
||||
|
||||
In this chapter, you'll learn:
|
||||
- How to return different Elements depending on a condition
|
||||
- How to conditionally include an Element in your structure
|
||||
- Common patterns like matching and bool mapping
|
||||
|
||||
## Conditionally returning Elements
|
||||
|
||||
In some components, you might want to render different markup given some condition. The typical example for conditional rendering is showing a "Log In" screen for users who aren't logged into your app. To break down this condition, we can consider two states:
|
||||
|
||||
- Logged in: show the app
|
||||
- Logged out: show the login screen
|
||||
|
||||
Using the knowledge from the previous section on components, we'll start by making the app's props:
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct AppProps {
|
||||
logged_in: bool
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have a "logged_in" flag accessible in our props, we can render two different screens:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope<AppProps>) -> Element {
|
||||
if props.logged_in {
|
||||
cx.render(rsx!{
|
||||
DashboardScreen {}
|
||||
})
|
||||
} else {
|
||||
cx.render(rsx!{
|
||||
LoginScreen {}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the user is logged in, then this component will return the DashboardScreen. Else, the component will render the LoginScreen.
|
||||
|
||||
## Using match statements
|
||||
|
||||
Rust provides us algebraic datatypes: enums that can contain values. Using the `match` keyword, we can execute different branches of code given a condition.
|
||||
|
||||
For instance, we could run a function that returns a Result:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
match get_name() {
|
||||
Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
|
||||
Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can even match against values:
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
match get_name() {
|
||||
"jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
|
||||
"diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )),
|
||||
name => cx.render(rsx!( "Hello, {name}!" )),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`.
|
||||
|
||||
To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
match get_name() {
|
||||
"jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
|
||||
"diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
|
||||
name => rsx!(cx, "Hello, {name}!" ),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This syntax even enables us to write a one-line component:
|
||||
```rust
|
||||
static App: Component = |cx| rsx!(cx, "hello world!");
|
||||
```
|
||||
|
||||
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope)-> Element {
|
||||
let greeting = match get_name() {
|
||||
"jack" => rsx!("Hey Jack, how's Diane?" ),
|
||||
"diane" => rsx!("Hey Diana, how's Jack?" ),
|
||||
name => rsx!("Hello, {name}!" ),
|
||||
};
|
||||
cx.render(greeting)
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting RSX
|
||||
|
||||
By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. We can include anything in our `rsx!` that implements `IntoVnodeList`: a marker trait for iterators that produce Elements. `rsx!` itself implements this trait, so we can include it directly:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
rsx!(
|
||||
"more rsx!"
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
As you might expect, we can refactor this structure into two separate calls using variables:
|
||||
|
||||
```rust
|
||||
let title = rsx!( "more rsx!" );
|
||||
|
||||
rsx!(
|
||||
div {
|
||||
title
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
In the case of a log-in screen, we might want to display the same NavBar and Footer for both logged in and logged out users. We can model this entirely by assigning a `screen` variable to a different Element depending on a condition:
|
||||
|
||||
|
||||
```rust
|
||||
let screen = match logged_in {
|
||||
true => rsx!(DashboardScreen {}),
|
||||
false => rsx!(LoginScreen {})
|
||||
};
|
||||
|
||||
cx.render(rsx!{
|
||||
Navbar {}
|
||||
screen,
|
||||
Footer {}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Boolean Mapping
|
||||
|
||||
In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element.
|
||||
|
||||
By default, Rust lets you convert any `boolean` into any other type by calling `and_then()`. We can exploit this functionality in components by mapping to some Element.
|
||||
|
||||
```rust
|
||||
let show_title = true;
|
||||
rsx!(
|
||||
div {
|
||||
show_title.and_then(|| rsx!{
|
||||
"This is the title"
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
We can use this pattern for many things, including options:
|
||||
```rust
|
||||
let user_name = Some("bob");
|
||||
rsx!(
|
||||
div {
|
||||
user_name.map(|name| rsx!("Hello {name}"))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Rendering Nothing
|
||||
|
||||
Sometimes, you don't want your component to return anything at all. Under the hood, the `Element` type is just an alias for `Option<VNode>`, so you can simply return `None`.
|
||||
|
||||
This can be helpful in certain patterns where you need to perform some logical side-effects but don't want to render anything.
|
||||
|
||||
```rust
|
||||
fn demo(cx: Scope) -> Element {
|
||||
None
|
||||
}
|
||||
```
|
||||
|
||||
## Moving Forward:
|
||||
|
||||
In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex User Interfaces!
|
||||
|
||||
In the next chapter, we'll cover how to renderer lists inside your `rsx!`.
|
||||
|
||||
Related Reading:
|
||||
- [RSX in Depth]()
|
||||
@@ -0,0 +1,297 @@
|
||||
|
||||
# Reusing, Importing, and Exporting Components
|
||||
|
||||
As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.
|
||||
|
||||
In this chapter we'll cover:
|
||||
- Rust's modules
|
||||
- Pub/Private components
|
||||
- Structure for large components
|
||||
|
||||
## Breaking it down
|
||||
Let's say our app looks something like this:
|
||||
|
||||
```shell
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(Scope) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct PostProps{}
|
||||
fn Post(Scope<PostProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct VoteButtonsProps {}
|
||||
fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct TitleCardProps {}
|
||||
fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct MetaCardProps {}
|
||||
fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ActionCardProps {}
|
||||
fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component.
|
||||
|
||||
## Breaking into different files
|
||||
|
||||
Fortunately, Rust has a built-in module system that's much cleaner than what you might be used to in JavaScript. Because `VoteButtons`, `TitleCard`, `MetaCard`, and `ActionCard` all belong to the `Post` component, let's put them all in a folder together called "post". We'll make a file for each component and move the props and render function.
|
||||
|
||||
```rust
|
||||
// src/post/action.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct ActionCardProps {}
|
||||
fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file.
|
||||
|
||||
```shell
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
├── main.rs
|
||||
└── post
|
||||
├── vote.rs
|
||||
├── title.rs
|
||||
├── meta.rs
|
||||
├── action.rs
|
||||
└── mod.rs
|
||||
```
|
||||
|
||||
|
||||
|
||||
In our `main.rs`, we'll want to declare the `post` module so we can access our `Post` component.
|
||||
|
||||
```rust
|
||||
// main.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
mod post;
|
||||
|
||||
fn App(Scope) -> Element {
|
||||
cx.render(rsx!{
|
||||
post::Post {
|
||||
id: Uuid::new_v4(),
|
||||
score: 10,
|
||||
comment_count: 10,
|
||||
post_time: std::Instant::now(),
|
||||
url: "example".to_string(),
|
||||
title: "Title".to_string(),
|
||||
original_poster: "me".to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
If you tried to build this app right now, you'll get an error message saying that `Post is private, trying changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
|
||||
|
||||
```rust
|
||||
// src/post/mod.rs
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct PostProps {}
|
||||
pub fn Post(Scope<PostProps>) -> Element {}
|
||||
```
|
||||
|
||||
While we're here, we also need to make sure each of our subcomponents are included as modules and exported.
|
||||
|
||||
Our "post/mod.rs" file will eventually look like this:
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
mod vote;
|
||||
mod title;
|
||||
mod meta;
|
||||
mod action;
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
pub struct PostProps {
|
||||
id: uuid::Uuid,
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: std::time::Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster: String
|
||||
}
|
||||
|
||||
pub fn Post(Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
vote::VoteButtons {
|
||||
score: props.score,
|
||||
}
|
||||
title::TitleCard {
|
||||
title: props.title,
|
||||
url: props.url,
|
||||
}
|
||||
meta::MetaCard {
|
||||
original_poster: props.original_poster,
|
||||
post_time: props.post_time,
|
||||
}
|
||||
action::ActionCard {
|
||||
post_id: props.id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Ultimately, including and exporting components is governed by Rust's module system. [The Rust book is a great resource to learn about these concepts in greater detail.](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
|
||||
|
||||
## Final structure:
|
||||
|
||||
```shell
|
||||
├── Cargo.toml
|
||||
└── src
|
||||
├── main.rs
|
||||
└── post
|
||||
├── vote.rs
|
||||
├── title.rs
|
||||
├── meta.rs
|
||||
├── action.rs
|
||||
└── mod.rs
|
||||
```
|
||||
|
||||
```rust
|
||||
// main.rs:
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::desktop::launch(App);
|
||||
}
|
||||
|
||||
mod post;
|
||||
|
||||
fn App(Scope) -> Element {
|
||||
cx.render(rsx!{
|
||||
post::Post {
|
||||
id: Uuid::new_v4(),
|
||||
score: 10,
|
||||
comment_count: 10,
|
||||
post_time: std::Instant::now(),
|
||||
url: "example".to_string(),
|
||||
title: "Title".to_string(),
|
||||
original_poster: "me".to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```rust
|
||||
// src/post/mod.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
mod vote;
|
||||
mod title;
|
||||
mod meta;
|
||||
mod action;
|
||||
|
||||
#[derive(Props, PartialEq)]
|
||||
pub struct PostProps {
|
||||
id: uuid::Uuid,
|
||||
score: i32,
|
||||
comment_count: u32,
|
||||
post_time: std::time::Instant,
|
||||
url: String,
|
||||
title: String,
|
||||
original_poster: String
|
||||
}
|
||||
|
||||
pub fn Post(Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
vote::VoteButtons {
|
||||
score: props.score,
|
||||
}
|
||||
title::TitleCard {
|
||||
title: props.title,
|
||||
url: props.url,
|
||||
}
|
||||
meta::MetaCard {
|
||||
original_poster: props.original_poster,
|
||||
post_time: props.post_time,
|
||||
}
|
||||
action::ActionCard {
|
||||
post_id: props.id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
// src/post/vote.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct VoteButtonsProps {}
|
||||
pub fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
// src/post/title.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct TitleCardProps {}
|
||||
pub fn TitleCard(Scope<TitleCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
// src/post/meta.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct MetaCardProps {}
|
||||
pub fn MetaCard(Scope<MetaCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
```rust
|
||||
// src/post/action.rs
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
pub struct ActionCardProps {}
|
||||
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
|
||||
```
|
||||
|
||||
## Moving forward
|
||||
|
||||
Next chapter, we'll start to add use code to hide and show Elements with conditional rendering.
|
||||
|
||||
For more reading on components:
|
||||
|
||||
- [Components in depth]()
|
||||
- [Lifecycles]()
|
||||
- [The Context object]()
|
||||
- [Optional Prop fields]()
|
||||
@@ -0,0 +1,73 @@
|
||||
# Core Topics
|
||||
|
||||
In this chapter, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
|
||||
|
||||
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
|
||||
|
||||
1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow.
|
||||
2) We declare how we want our state to change when the user triggers an event.
|
||||
|
||||
## Declarative UI
|
||||
|
||||
Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences.
|
||||
|
||||
Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options.
|
||||
|
||||
|
||||
Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight.
|
||||
|
||||
```rust
|
||||
let container = Container::new();
|
||||
|
||||
let green_light = Light::new().color("green").enabled(true);
|
||||
let yellow_light = Light::new().color("yellow").enabled(false);
|
||||
let red_light = Light::new().color("red").enabled(false);
|
||||
container.push(green_light);
|
||||
container.push(yellow_light);
|
||||
container.push(red_light);
|
||||
|
||||
container.set_onclick(move |_| {
|
||||
if red_light.enabled() {
|
||||
red_light.set_enabled(false);
|
||||
green_light.set_enabled(true);
|
||||
} else if yellow_light.enabled() {
|
||||
yellow_light.set_enabled(false);
|
||||
red_light.set_enabled(true);
|
||||
} else if green_light.enabled() {
|
||||
green_light.set_enabled(false);
|
||||
yellow_light.set_enabled(true);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience.
|
||||
|
||||
Instead, with Dioxus, we *declare* what we want our UI to look like:
|
||||
|
||||
```rust
|
||||
let mut state = use_state(&cx, || "red");
|
||||
|
||||
cx.render(rsx!(
|
||||
Container {
|
||||
Light { color: "red", enabled: state == "red", }
|
||||
Light { color: "yellow", enabled: state == "yellow", }
|
||||
Light { color: "green", enabled: state == "green", }
|
||||
|
||||
onclick: move |_| {
|
||||
state.set(match *state {
|
||||
"green" => "yellow",
|
||||
"yellow" => "red",
|
||||
"red" => "green",
|
||||
})
|
||||
}
|
||||
}
|
||||
))
|
||||
```
|
||||
|
||||
Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.
|
||||
|
||||
Here's some reading about declaring UI in React:
|
||||
|
||||
- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js)
|
||||
|
||||
- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44)
|
||||
@@ -0,0 +1,174 @@
|
||||
# Conditional Lists and Keys
|
||||
|
||||
You will often want to display multiple similar components from a collection of data.
|
||||
|
||||
In this chapter, you will learn:
|
||||
|
||||
- How to use iterators in `rsx!`
|
||||
- How to filter and transform data into a list of Elements
|
||||
- How to create efficient lists with keys
|
||||
|
||||
## Rendering data from lists
|
||||
|
||||
Thinking back to our analysis of the `r/reddit` page, we notice a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app like:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
Post {/* some properties */}
|
||||
Post {/* some properties */}
|
||||
Post {/* some properties */}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Instead, we need to transform the list of data into a list of Elements.
|
||||
|
||||
For convenience, `rsx!` supports any type in curly braces that implements the `IntoVnodeList` trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements `IntoVnodeList`.
|
||||
|
||||
As a simple example, let's render a list of names. First, start with our input data:
|
||||
|
||||
```rust
|
||||
let names = ["jim", "bob", "jane", "doe"];
|
||||
```
|
||||
|
||||
Then, we create a new iterator by calling `iter` and then `map`. In our `map` function, we'll place render our template.
|
||||
|
||||
```rust
|
||||
let name_list = names.iter().map(|name| rsx!(
|
||||
li { "{name}" }
|
||||
));
|
||||
```
|
||||
|
||||
Finally, we can include this list in the final structure:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
ul {
|
||||
{name_list}
|
||||
}
|
||||
)
|
||||
```
|
||||
The HTML-rendered version of this list would follow what you would expect:
|
||||
```html
|
||||
<ul>
|
||||
<li> jim </li>
|
||||
<li> bob </li>
|
||||
<li> jane </li>
|
||||
<li> doe </li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Rendering our posts with a PostList component
|
||||
|
||||
Let's start by modeling this problem with a component and some properties.
|
||||
|
||||
For this example, we're going to use the borrowed component syntax since we probably have a large list of posts that we don't want to clone every time we render the Post List.
|
||||
|
||||
```rust
|
||||
#[derive(Props, PartialEq)]
|
||||
struct PostListProps<'a> {
|
||||
posts: &'a [PostData]
|
||||
}
|
||||
```
|
||||
Next, we're going to define our component:
|
||||
|
||||
```rust
|
||||
fn App(cx: Scope<PostList?) -> Element {
|
||||
// First, we create a new iterator by mapping the post array
|
||||
let posts = cx.props.posts.iter().map(|post| rsx!{
|
||||
Post {
|
||||
title: post.title,
|
||||
age: post.age,
|
||||
original_poster: post.original_poster
|
||||
}
|
||||
});
|
||||
|
||||
// Finally, we render the post list inside of a container
|
||||
cx.render(rsx!{
|
||||
ul { class: "post-list"
|
||||
{posts}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Filtering Iterators
|
||||
|
||||
Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check.
|
||||
|
||||
As a very simple example, let's set up a filter where we only list names that begin with the letter "J".
|
||||
|
||||
Let's make our list of names:
|
||||
|
||||
```rust
|
||||
let names = ["jim", "bob", "jane", "doe"];
|
||||
```
|
||||
|
||||
Then, we create a new iterator by calling `iter`, then `filter`, then `map`. In our `filter` function, we'll only allow "j" names, and in our `map` function, we'll render our template.
|
||||
|
||||
```rust
|
||||
let name_list = names
|
||||
.iter()
|
||||
.filter(|name| name.starts_with('j'))
|
||||
.map(|name| rsx!( li { "{name}" }));
|
||||
```
|
||||
|
||||
Rust's iterators provide us tons of functionality and are significantly easier to work with than JavaScript's map/filter/reduce.
|
||||
|
||||
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call.
|
||||
|
||||
The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.
|
||||
|
||||
## Keeping list items in order with key
|
||||
|
||||
|
||||
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: a lack of "keys". Whenever you render a list of elements, each item in the list must be **uniquely identifiable**. To make each item unique, you need to give it a "key".
|
||||
|
||||
In Dioxus, keys are strings that uniquely identifies it among other items in that array:
|
||||
|
||||
```rust
|
||||
rsx!( li { key: "a" } )
|
||||
```
|
||||
|
||||
Keys tell Dioxus which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps Dioxus infer what exactly has happened, and make the correct updates to the screen
|
||||
|
||||
|
||||
NB: the language from this section is strongly borrowed from [React's guide on keys](https://reactjs.org/docs/lists-and-keys.html).
|
||||
### Where to get your key
|
||||
|
||||
Different sources of data provide different sources of keys:
|
||||
|
||||
- _Data from a database_: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.
|
||||
- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter or a package like `uuid` when creating items.
|
||||
|
||||
### Rules of keys
|
||||
|
||||
- Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays.
|
||||
- Keys must not change or that defeats their purpose! Don’t generate them while rendering.
|
||||
|
||||
### Why does Dioxus need keys?
|
||||
|
||||
Imagine that files on your desktop didn’t have names. Instead, you’d refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on.
|
||||
|
||||
File names in a folder and Element keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime.
|
||||
|
||||
### Gotcha
|
||||
You might be tempted to use an item’s index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.
|
||||
|
||||
Similarly, do not generate keys on the fly, `gen_random`. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data.
|
||||
|
||||
Note that your components won’t receive key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
|
||||
```rust
|
||||
Post { key: "{key}", id: "{id}" }
|
||||
```
|
||||
|
||||
## Moving on
|
||||
|
||||
In this section, we learned:
|
||||
- How to render lists of data
|
||||
- How to use iterator tools to filter and transform data
|
||||
- How to use keys to render lists efficiently
|
||||
|
||||
Moving forward, we'll finally cover user input and interactivity.
|
||||
@@ -0,0 +1,173 @@
|
||||
# Declaring your first UI with Elements
|
||||
|
||||
Every user interface you've ever used is just a symphony of tiny widgets working together to abstract over larger complex functions. In Dioxus, we call these tiny widgets "Elements." Using Components, you can easily compose Elements into larger groups to form even larger structures: Apps.
|
||||
|
||||
In this chapter, we'll cover:
|
||||
- Declaring our first Element
|
||||
- Composing Elements together
|
||||
- Element properties
|
||||
|
||||
## Declaring our first Element
|
||||
Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {}
|
||||
)
|
||||
```
|
||||
As you might expect, we can render this call using Dioxus-SSR to produce valid HTML:
|
||||
|
||||
```rust
|
||||
dioxus::ssr::render_lazy(rsx!(
|
||||
div {}
|
||||
))
|
||||
```
|
||||
|
||||
Produces:
|
||||
```html
|
||||
<div></div>
|
||||
```
|
||||
|
||||
We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration.
|
||||
## Composing Elements
|
||||
|
||||
Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other.
|
||||
|
||||
```rust
|
||||
#use dioxus::prelude::*;
|
||||
rsx!(
|
||||
div {
|
||||
h1 {}
|
||||
h2 {}
|
||||
p {}
|
||||
}
|
||||
)
|
||||
```
|
||||
As you might expect, the generated HTML for this structure would look like:
|
||||
```html
|
||||
<div>
|
||||
<h1></h1>
|
||||
<h2></h2>
|
||||
<p></p>
|
||||
</div>
|
||||
```
|
||||
|
||||
With the default configuration, any Element defined within the `dioxus-html` crate can be declared in this way. To create your own new elements, see the `Custom Elements` Advanced Guide.
|
||||
|
||||
## Text Elements
|
||||
|
||||
Dioxus also supports a special type of Element: Text. Text Elements do not accept children, but rather just string literals denoted with double quotes.
|
||||
|
||||
```rust
|
||||
rsx! (
|
||||
"hello world"
|
||||
)
|
||||
```
|
||||
|
||||
Text Elements can be composed within other Elements:
|
||||
```rust
|
||||
rsx! (
|
||||
div {
|
||||
h1 { "hello world" }
|
||||
p { "Some body content" }
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Text can also be formatted with any value that implements `Display`. We use [f-string formatting](https://docs.rs/fstrings/0.2.3/fstrings/) - a "coming soon" feature for stable Rust that is familiar for Python and JavaScript users:
|
||||
|
||||
```rust
|
||||
let name = "Bob";
|
||||
rsx! ( "hello {name}" )
|
||||
```
|
||||
|
||||
Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of how the `rsx!` macro (we'll cover later), our call to `format_args` must be contained within curly braces *and* square braces.
|
||||
|
||||
```rust
|
||||
rsx!( {[format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )]} )
|
||||
```
|
||||
|
||||
Alternatively, `&str` can be included directly, though it must be inside of an array:
|
||||
|
||||
```rust
|
||||
rsx!( "Hello ", [if enabled { "Jack" } else { "Bob" }] )
|
||||
```
|
||||
|
||||
This is different from React's way of generating arbitrary markup but fits within idiomatic Rust.
|
||||
|
||||
Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call and leverage the f-string formatting:
|
||||
|
||||
```rust
|
||||
let name = if enabled { "Jack" } else { "Bob" };
|
||||
rsx! ( "hello {name}" )
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
Every Element in your User Interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.
|
||||
|
||||
To do this, we use the familiar struct-style syntax that Rust provides:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
hidden: "true",
|
||||
background_color: "blue",
|
||||
class: "card color-{mycolor}"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Each field is defined as a method on the element in the `dioxus-html` crate. This prevents you from misspelling a field name and lets us provide inline documentation. When you need to use a field not defined as a method, you have two options:
|
||||
|
||||
1) file an issue if the attribute _should_ be enabled
|
||||
2) add a custom attribute on-the-fly
|
||||
|
||||
To use custom attributes, simply put the attribute name in quotes followed by a colon:
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
"customAttr": "important data here"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
> Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. When using custom attributes, make sure the name of the attribute **exactly** matches what the renderer is expecting.
|
||||
|
||||
All element attributes must occur *before* child elements. The `rsx!` macro will throw an error if your child elements come before any of your attributes. If you don't see the error, try editing your Rust-Analyzer IDE setting to ignore macro-errors. This is a temporary workaround because Rust-Analyzer currently throws *two* errors instead of just the one we care about.
|
||||
|
||||
```rust
|
||||
// settings.json
|
||||
{
|
||||
"rust-analyzer.diagnostics.disabled": [
|
||||
"macro-error"
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## Listeners
|
||||
|
||||
Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered.
|
||||
|
||||
We'll cover listeners in more depth in the Listeners chapter, but for now, just know that every listener must start with the `on` keyword and accepts closures.
|
||||
|
||||
```rust
|
||||
rsx!(
|
||||
div {
|
||||
onclick: move |_| log::debug!("div clicked!"),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Moving On
|
||||
|
||||
This chapter just scratches the surface on how Elements can be defined.
|
||||
|
||||
We learned:
|
||||
- Elements are the basic building blocks of User Interfaces
|
||||
- Elements can contain other elements
|
||||
- Elements can either be a named container or text
|
||||
- Some Elements have properties that the renderer can use to draw the UI to the screen
|
||||
|
||||
Next, we'll compose Elements together to form components.
|
||||
Reference in new issue
Block a user