docs: create a new "advanced" book

This commit is contained in:
Jonathan Kelley committed 2021-10-29 00:40:36 -04:00
1 parent 54d050cf89
commit 97a2c62804
69 files changed
+24 -9

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
# 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.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 state = "red";
rsx!(
Container {
Light { color: "red", enabled: {state == "red"} }
Light { color: "yellow", enabled: {state == "yellow"} }
Light { color: "green", enabled: {state == "green"} }
onclick: |_| {
state = 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,33 @@
# Subscriptions
Yew subscriptions are used to schedule update for components into the future. The `Context` object can create subscriptions:
```rust
fn Component(cx: Component<()>) -> DomTree {
let update = cx.schedule();
// Now, when the subscription is called, the component will be re-evaluated
update.consume();
}
```
Whenever a component's subscription is called, the component will then re-evaluated. You can consider the input properties of
a component to be just another form of subscription. By default, the Dioxus component system automatically diffs a component's props
when the parent function is called, and if the props are different, the child component's subscription is called.
The subscription API exposes this functionality allowing hooks and state management solutions the ability to update components whenever
some state or event occurs outside of the component. For instance, the `use_context` hook uses this to subscribe components that use a
particular context.
```rust
fn use_context<I>(cx: Context<T>) -> I {
}
```
@@ -0,0 +1,82 @@
# Concurrent mode
Concurrent mode provides a mechanism for building efficient asynchronous components. With this feature, components don't need to render immediately, and instead can schedule a future render by returning a future.
To make a component asynchronous, simply change its function signature to async.
```rust
fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
becomes
```rust
async fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
Now, logic in components can be awaited to delay updates of the component and its children. Like so:
```rust
async fn Example(cx: Context<()>) -> Vnode {
let name = fetch_name().await;
rsx!{ <div> "Hello {name}" </div> }
}
async fetch_name() -> String {
// ...
}
```
This component will only schedule its render once the fetch is complete. However, we _don't_ recommend using async/await directly in your components.
Async is a notoriously challenging yet rewarding tool for efficient tools. If not careful, locking and unlocking shared aspects of the component's context can lead to data races and panics. If a shared resource is locked while the component is awaiting, then other components can be locked or panic when trying to access the same resource. These rules are especially important when references to shared global state are accessed using the context object's lifetime. If mutable references to data captured immutably by the context are taken, then the component will panic, and cause confusion.
Instead, we suggest using hooks and future combinators that can safely utilize the safe guards of the component's Context when interacting with async tasks.
As part of our Dioxus hooks crate, we provide a data loader hook which pauses a component until its async dependencies are ready. This caches requests, reruns the fetch if dependencies have changed, and provides the option to render something else while the component is loading.
```rust
async fn ExampleLoader(cx: Context<()>) -> Vnode {
/*
Fetch, pause the component from rendering at all.
The component is locked while waiting for the request to complete
While waiting, an alternate component is scheduled in its place.
This API stores the result on the Context object, so the loaded data is taken as reference.
*/
let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
.place_holder(|(cx, props)|rsx!{<div> "loading..." </div>})
.delayed_place_holder(1000, |(cx, props)|rsx!{ <div> "still loading..." </div>})
.await;
match name {
Ok(name) => rsx! { <div> "Hello {something}" </div> },
Err(e) => rsx! { <div> "An error occurred :(" </div>}
}
}
```
```rust
async fn Example(cx: Context<()>) -> DomTree {
// Diff this set between the last set
// Check if we have any outstanding tasks?
//
// Eventually, render the component into the VDOM when the future completes
<div>
<Example />
</div>
// Render a div, queue a component
// Render the placeholder first, then when the component is ready, then render the component
<div>
<Suspense placeholder={html!{<div>"Loading"</div>}}>
<Example />
</Suspense>
</div>
}
```
+56
View File
@@ -0,0 +1,56 @@
# Memoization and the arena allocator
Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator.
One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to any and allocated on the heap - not in the arena with the components.
With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change.
https://dmitripavlutin.com/use-react-memo-wisely/
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
```rust
fn test() -> DomTree {
html! {
<>
<SomeComponent nomemo />
// same as
<SomeComponent nomemo=true />
</>
}
}
static TestComponent: FC<()> = |(cx, props)|html!{<div>"Hello world"</div>};
static TestComponent: FC<()> = |(cx, props)|{
let g = "BLAH";
html! {
<div> "Hello world" </div>
}
};
#[functional_component]
static TestComponent: FC<{ name: String }> = |(cx, props)|html! { <div> "Hello {name}" </div> };
```
## Why this behavior?
"This is different than React, why differ?".
Take a component likes this:
```rust
fn test(cx: Context<()>) -> DomTree {
let Bundle { alpha, beta, gamma } = use_context::<SomeContext>(cx);
html! {
<div>
<Component name=alpha />
<Component name=beta />
<Component name=gamma />
</div>
}
}
```
While the contents of the destructured bundle might change, not every child component will need to be re-rendered.
+155
View File
@@ -0,0 +1,155 @@
# Signals: Skipping the Diff
In most cases, the traditional VirtualDOM diffing pattern is plenty fast. Dioxus will compare trees of VNodes, find the differences, and then update the Renderer's DOM with the updates. However, this can generate a lot of overhead for certain types of components. In apps where reducing visual latency is a top priority, you can opt into the `Signals` api to entirely disable diffing of hot-path components. Dioxus will then automatically construct a state machine for your component, making updates nearly instant.
Signals build on the same infrastructure that powers asynchronous rendering where in-tree values can be updated outside of the render phase. In async rendering, a future is used as the signal source. With the raw signal API, any value can be used as a signal source.
By default, Dioxus will only try to diff subtrees of components with dynamic content, automatically skipping diffing static content.
## What does this look like?
Your component today might look something like this:
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
input {
value: title,
onchange: move |new| set_title(new.value())
}
})
}
```
This component is fairly straightforward - the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
div {
input {
value: title,
onchange: move |new| set_title(new.value())
}
ul {
{0..10000.map(|f| rsx!{
li { "{f}" }
})}
}
}
})
}
```
Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates.
We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let mut title = use_signal(&cx, || String::from("Title"));
cx.render(rsx!(input { value: title }))
}
```
For a slightly more interesting example, this component calculates the sum between two numbers, but totally skips the diffing process.
```rust
fn Calculator(cx: Context<()>) -> DomTree {
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
let mut c = a + b;
rsx! {
input { value: a }
input { value: b }
p { "a + b = {c}" }
}
}
```
Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex that a basic operation (`std::ops`) we can either chain signals together or combine them:
```rust
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
// Chain signals together using the `with` method
let c = a.with(b).map(|(a, b)| *a + *b);
```
## Deref and DerefMut
If we ever need to get the value out of a signal, we can simply `deref` it.
```rust
let mut a = use_signal(&cx, || 0);
let c = *a + *b;
```
Calling `deref` or `derefmut` is actually more complex than it seems. When a value is derefed, you're essentially telling Dioxus that _this_ element _needs_ to be subscribed to the signal. If a signal is derefed outside of an element, the entire component will be subscribed and the advantage of skipping diffing will be lost. Dioxus will throw an error in the console when this happens to tell you that you're using signals wrong, but your component will continue to work.
## Global Signals
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
```rust
const TITLE: Atom<String> = || "".to_string();
const Provider: FC<()> = |(cx, props)|{
let title = use_signal(&cx, &TITLE);
rsx!(cx, input { value: title })
};
```
If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
```rust
const Receiver: FC<()> = |(cx, props)|{
let title = use_signal(&cx, &TITLE);
log::info!("This will only be called once!");
rsx!(cx,
div {
h1 { "{title}" }
div {}
footer {}
}
)
};
```
Dioxus knows that the receiver's `title` signal is used only in the text node, and skips diffing Receiver entirely, knowing to update _just_ the text node.
If you build a complex app on top of Dirac, you'll likely notice that many of your components simply won't be diffed at all. For instance, our Receiver component will never be diffed once it has been mounted!
## Signals and Iterators
Sometimes you want to use a collection of items. With Signals, you can bypass diffing for collections - a very powerful technique to avoid re-rendering on large collections.
By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to.
Dioxus automatically understands how to use your signals when mixed with iterators through Deref/DerefMut. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
```rust
const DICT: AtomFamily<String, String> = |_| {};
const List: FC<()> = |(cx, props)|{
let dict = use_signal(&cx, &DICT);
cx.render(rsx!(
ul {
For { each: dict, map: |k, v| rsx!( li { "{v}" }) }
}
))
};
```
## Remote Signals
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
```rust
```
## How does it work?
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
+54
View File
@@ -0,0 +1,54 @@
# Subtrees
One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase.
For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of:
Root
-> Tree 1
-> Tree 2
-> Original root tree
- Root edits
- Tree 1 Edits
- Tree 2 Edits
- Root Edits
The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom.
With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept.
The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows.
Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins.
## API
Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots.
```rust
fn Subtree<P>(cx: Context, props: P) -> DomTree {
}
fn Window() -> DomTree {
Subtree {
onassign: move |e| {
// create window
}
children()
}
}
fn 3dRenderer -> DomTree {
Subtree {
onassign: move |e| {
// initialize bevy
}
}
}
```
+1
View File
@@ -0,0 +1 @@
# Working with Async
@@ -0,0 +1 @@
# Async Callbacks
+1
View File
@@ -0,0 +1 @@
# Tasks
+1
View File
@@ -0,0 +1 @@
# Bundling and Distributing
+191
View File
@@ -0,0 +1,191 @@
# 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.
![Reddit Post](../images/reddit_post.png)
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:
![Post as Component](../images/reddit_post_components.png)
- **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, props): Component<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
VoteButton {
score: props.score,
}
TitleCard {
title: props.title,
url: props.url,
}
MetaCard {
original_poster: props.original_poster,
post_time: props.post_time,
}
ActionCard {
post_id: 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 tuple of `Context` and `&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, props): Component<VoteButtonProps>) -> Element {
cx.render(rsx!{
div { class: "votebutton"
div { class: "arrow up" }
div { class: "score", "{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, props): Component<'a, TitleCardProps>) -> Element<'a> {
cx.render(rsx!{
h1 { "{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 `Context` object
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Context` 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 `Context` object to maintain some internal bookkeeping. That's what the `Context` 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 `Context` object to build robust and performant extensions for Dioxus.
```rust
fn Post((cx /* <-- our Context object*/, props): Component<PostProps>) -> Element {
cx.render(rsx!{ })
}
```
## 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 Context object]()
- [Optional Prop fields]()
@@ -0,0 +1,175 @@
# 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, props): Component<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, props): Component<()>) -> 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, props): Component<()>) -> 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. Because every closure is unique, we cannot return *just* `rsx!` from a match statement. Every `rsx!` call must be rendered first when used in match statements.
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, props): Component<()>) -> 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: Fc<()> = |(cx, props)| rsx!(cx, "hello world!");
```
## Nesting RSX
By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. With the curly-brace syntax, 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!(cx, DashboardScreen {}),
false => rsx!(cx, 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. In these cases, we can pass "None" into our bracket. However, Rust is not able to infer that our `None` corresponds to `Element` so we need to cast it appropriately:
```rust
rsx!(cx, {None as Element} )
```
## 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 @@
# Custom Elements
@@ -0,0 +1 @@
# Custom Renderer
+1
View File
@@ -0,0 +1 @@
# Effects
+11
View File
@@ -0,0 +1,11 @@
# Error handling
Astute observers might have noticed that `Element` is actually a type alias for `Option<VNode>`. You don't need to know what a `VNode` is, but it's important to recognize that we could actually return nothing at all:
```rust
fn App((cx, props): Component<()>) -> Element {
None
}
```
@@ -0,0 +1 @@
# Event handlers
@@ -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, |c| c);
}
fn App((cx, props): Component<()>) -> Element {}
#[derive(PartialEq, Props)]
struct PostProps{}
fn Post((cx, props): Component<PostProps>) -> Element {}
#[derive(PartialEq, Props)]
struct VoteButtonsProps {}
fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {}
#[derive(PartialEq, Props)]
struct TitleCardProps {}
fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct MetaCardProps {}
fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard((cx, props): Component<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((cx, props): Component<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, |c| c);
}
mod post;
fn App((cx, props): Component<()>) -> 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((cx, props): Component<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((cx, props): Component<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, |c| c);
}
mod post;
fn App((cx, props): Component<()>) -> 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((cx, props): Component<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((cx, props): Component<VoteButtonsProps>) -> Element {}
```
```rust
// src/post/title.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct TitleCardProps {}
pub fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
```
```rust
// src/post/meta.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct MetaCardProps {}
pub fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
```
```rust
// src/post/action.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct ActionCardProps {}
pub fn ActionCard((cx, props): Component<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]()
+1
View File
@@ -0,0 +1 @@
# Hooks and Internal State
+1
View File
@@ -0,0 +1 @@
# Adding Interactivity
+1
View File
@@ -0,0 +1 @@
# Lifecycle, updates, and effects
+174
View File
@@ -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, props): Component<PostList>) -> Element {
// First, we create a new iterator by mapping the post array
let posts = 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 @@
# Managing State
+1
View File
@@ -0,0 +1 @@
# Memoization
+63
View File
@@ -0,0 +1,63 @@
# VNodes with RSX, HTML, and NodeFactory
Many modern frameworks provide a domain-specific-language for declaring user-interfaces. In the case of React, this language extension is called JSX and must be handled through additional dependencies and pre/post processors to transform your source code. With Rust, we can simply provide a procedural macro in the Dioxus dependency itself that mimics the JSX language.
With Dioxus, we actually ship two different macros - a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls.
For instance, this html! call:
```rust
html!(<div> "hello world" </div>)
```
becomes this NodeFactory call:
```rust
|f| f.element(
dioxus_elements::div, // tag
[], // listeners
[], // attributes
[f.static_text("hello world")], // children
None // key
)
```
The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" - the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes.
![Compile time correct syntax](../images/compiletimecorrect.png)
## html! macro
The html! macro supports a limited subset of the html standard. Rust's macro parsing tools are somewhat limited, so all text between tags _must be quoted_.
However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX - it's more natural for Rust programs and _does_ integrate well with Rust IDE tools.
```rust
let name = "jane";
let pending = false;
let count = 10;
dioxus::ssr::render_lazy(html! {
<div>
<p> "Hello, {name}!" </p>
<p> "Status: {pending}!" </p>
<p> "Count {count}!" </p>
</div>
});
```
## rsx! macro
The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, section selecting, inline documentation, GOTO definition, and refactoring support.
When helpful, the Dioxus VSCode extension provides a way of converting a selection of HTML directly to RSX, so you can import templates from the web directly into your existing app.
It's also a bit easier on the eyes than HTML.
```rust
dioxus::ssr::render_lazy(rsx! {
div {
p {"Hello, {name}!"}
p {"Status: {pending}!"}
p {"Count {count}!"}
}
});
```
In the next section, we'll cover the `rsx!` macro in more depth.
+229
View File
@@ -0,0 +1,229 @@
# RSX in Depth
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
```rust
rsx!(div {
button {
"Add todo",
onclick: move |e| todos.write().new_todo()
}
ul {
class: "todo-list"
(todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
)))
}
})
```
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `reference` guide has plenty of examples on how to use `rsx!` effectively.
### Element structure
```rust
div {
hidden: false,
"some text"
child {}
Component {}
{/* literal tokens that resolve to vnodes */}
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets a property on this element.
- `"text"` adds a new text element
- `tag {}` adds a new child element
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
### Ignoring `cx.render` with `rsx!(cx, ...)`
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
```rust
cx.render(rsx!( div {} ))
// becomes
rsx!(cx, div {})
```
### Conditional Rendering
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
```rust
rsx!({
if enabled {
rsx!(cx, div {"enabled"})
} else {
rsx!(cx, li {"disabled"})
}
})
```
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
```rust
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
```rust
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => rsx!(cx, div {}),
false => rsx!(cx, div {})
}
```
### Lists
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
```rust
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Sometimes, it makes sense to render VNodes into a list:
```rust
let mut items = vec![];
for _ in 0..5 {
items.push(rsx!(cx, li {} ))
}
rsx!(cx, {items} )
```
#### Lists and Keys
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
```rust
fn render_list(cx: Context, items: HashMap<String, Todo>) -> DomTree {
rsx!(cx, ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Complete Reference
```rust
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => rsx!(cx, h1 { "big" })
2 => rsx!(cx, h2 { "medium" })
_ => rsx!(cx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.children()}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```
@@ -0,0 +1 @@
# Server-side components
+1
View File
@@ -0,0 +1 @@
# Global State
+1
View File
@@ -0,0 +1 @@
# Suspense
+29
View File
@@ -0,0 +1,29 @@
## Testing
To test your Rust code, you can annotate any function with the `#[test]` block. In VSCode with RA, this will provide a lens to click and run the test.
```rust
#[test]
fn component_runs() {
assert!(true)
}
```
This will test your Rust code _without_ going through the browser. This is ideal for squashing logic bugs and ensuring components render appropriately when the browsers's DOM is not needed. If you need to run tests in the browser, you can annotate your blocks with the `#[dioxus::test]` block.
```rust
#[dioxus::test]
fn runs_in_browser() {
// ...
}
```
Then, when you run
```
$ dioxus test --chrome
```
Dioxus will build and test your code using the Chrome browser as a harness.
There's a lot more to testing if you dive in, so check out the [Testing]() guide for more information
+159
View File
@@ -0,0 +1,159 @@
# 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
#use dioxus::prelude::*;
rsx!(
div {}
)
```
As you might expect, we can render this call using Dioxus-SSR to produce valid HTML:
```rust
#use dioxus::prelude::*;
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" } )]} )
```
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:
```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. Commas are optional:
```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.
## 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 can accept either a closure or an expression wrapped in curly braces.
```rust
rsx!(
div {
onclick: move |_| {}
onmouseover: {handler},
}
)
```
## 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.