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:


#![allow(unused)]
fn main() {
#[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:


#![allow(unused)]
fn main() {
fn App(cx: Scope<AppProps>) -> Element {
    if cx.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. If they're not logged in, 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:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
fn App(cx: Scope)-> Element {
    match get_name() {
        "jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
        "diane" => cx.render(rsx!( "Hey Diane, how's Jack?" )),
        name => cx.render(rsx!( "Hello, {name}!" )),
    }
}
}

Do note: the rsx! macro does not return an Element, but rather a wrapper struct for a Closure (an anonymous function). To turn our rsx! into an Element, 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:


#![allow(unused)]
fn main() {
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}!" ),
    }
}
}

Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to cx.render:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
rsx!(
    div {
        rsx!(
            "more rsx!"
        )
    }
)
}

As you might expect, we can refactor this structure into two separate calls using variables:


#![allow(unused)]
fn main() {
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:


#![allow(unused)]
fn main() {
let screen = match logged_in {
    true => rsx!(DashboardScreen {}),
    false => rsx!(LoginScreen {})
};

cx.render(rsx!{
    Navbar {}
    screen,
    Footer {}
})
}

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.


#![allow(unused)]
fn main() {
fn demo(cx: Scope) -> Element {
    None
}
}

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 an Option of any other type with then(). We can use this in Components by mapping to some Element.


#![allow(unused)]
fn main() {
let show_title = true;
rsx!(
    div {
        // Renders nothing by returning None when show_title is false
        show_title.then(|| rsx!{
            "This is the title"
        })
    }
)
}

We can use this pattern for many things, including options:


#![allow(unused)]
fn main() {
let user_name = Some("bob");
rsx!(
    div {
        // Renders nothing if user_name is None
        user_name.map(|name| rsx!("Hello {name}"))
    }
)
}

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!.