Catch All Routes

Many modern web apps store parameters within their current path. This allows users to share URLs that link to a specific bit of content. We can create this functionality with catch all routes.

If you want to change what route is active based on the format of the parameter, see Matching Routes.

The parameter will be URL decoded.

Creating a content component

We start by creating a component that uses the parameters value.

We can get the current state of the router using the use_route hook. From that state we can extract the current value of our parameter by using a key we will later also define on our route.

It is VERY IMPORTANT to drop the object returned by the use_route hook once our component finished rendering. Otherwise the entire router will be frozen.

The use_route hook can only be used in components nested within a component that called use_router.


#![allow(unused)]
fn main() {
// Hidden lines (like this one) make the documentation tests work.
extern crate dioxus;
use dioxus::prelude::*;
extern crate dioxus_router;
use dioxus_router::prelude::*;

struct Name;

fn Greeting(cx: Scope) -> Element {
    let route = use_route(cx).expect("is nested within a Router component");
    let name = route.parameter::<Name>()
        .map(|name| name.clone())
        .unwrap_or(String::from("world"));

    render! {
        p { "Hello, {name}!" }
    }
}
}

Defining the routes

Now we can define our route. Unlike a fixed Route, a ParameterRoute needs two arguments to be created.

Also note that each Segment can have exactly one parameter or fallback route.

For that reason, the example below would not work in practice, but showing both forms (explicit and short) is more important for this example.


#![allow(unused)]
fn main() {
// Hidden lines (like this one) make the documentation tests work.
extern crate dioxus;
use dioxus::prelude::*;
extern crate dioxus_router;
use dioxus_router::prelude::*;
fn Greeting(cx: Scope) -> Element { unimplemented!() }

struct Name;

fn App(cx: Scope) -> Element {
    use_router(
        cx,
        &|| RouterConfiguration {
            ..Default::default()
        },
        &|| {
           Segment::empty()
            .catch_all(ParameterRoute::content::<Name>(comp(Greeting)))
            .catch_all((comp(Greeting), Name { })) // same in short
        }
    );

    // ...
    unimplemented!()
}
}

Interaction with other routes

Each individual Segment can only ever have one active route. This means that when a Segment has more than just a catch all route, the router has to decide which is active. It does that this way:

  1. If the segment is not specified (i.e. /), then the index route will be active.
  2. If a fixed route matches the current path, it will be active.
  3. If a matching route matches the current path, it will be active. Matching routes are checked in the order they are defined.
  4. If neither a fixed nor a matching route is active, the catch all route or fallback route will be.

Step 0 means that if we want a parameter to be empty, that needs to be specified by the path, i.e. //.

Be careful with using catch all routes on the root Segment. Navigating to paths starting with // will NOT work. This is not a limitation of the router, but rather of how relative URLs work.

If you absolutely need an empty parameter on the root Segment, a URL like this could work:

  • https://your-site.example// for web sites
  • dioxus://index.html// for desktop apps

Full Code


#![allow(unused)]
fn main() {
// Hidden lines (like this one) make the documentation tests work.
extern crate dioxus;
use dioxus::prelude::*;
extern crate dioxus_router;
use dioxus_router::{history::MemoryHistory, prelude::*};
extern crate dioxus_ssr;

struct Name;

fn Greeting(cx: Scope) -> Element {
    let route = use_route(cx).expect("is nested within a Router component");
    let name = route.parameter::<Name>()
        .map(|name| name.clone())
        .unwrap_or(String::from("world"));

    render! {
        p { "Hello, {name}!" }
    }
}

fn App(cx: Scope) -> Element {
    let routes = use_router(
        cx,
        &|| RouterConfiguration {
            synchronous: true,
            history: Box::new(MemoryHistory::with_initial_path("/Dioxus").unwrap()),
            ..Default::default()
        },
        &|| Segment::empty().catch_all((comp(Greeting), Name { }))
    );
    // ...
    render! {
        Outlet { }
    }
}

let mut vdom = VirtualDom::new(App);
vdom.rebuild();
assert_eq!(
    dioxus_ssr::render(&vdom),
    "<p>Hello, Dioxus!</p>"
);
}