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:
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:
Using the knowledge from the previous section on components, we'll start by making the app's props:
#[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:
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.
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:
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:
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:
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:
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
:
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)
}
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:
rsx!(
div {
rsx!(
"more rsx!"
)
}
)
As you might expect, we can refactor this structure into two separate calls using variables:
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:
let screen = match logged_in {
true => rsx!(DashboardScreen {}),
false => rsx!(LoginScreen {})
};
cx.render(rsx!{
Navbar {}
screen,
Footer {}
})
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.
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:
let user_name = Some("bob");
rsx!(
div {
user_name.map(|name| rsx!("Hello {name}"))
}
)
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.
fn demo(cx: Scope) -> Element {
None
}
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: