dioxus icon indicating copy to clipboard operation
dioxus copied to clipboard

Multiple routes with the same component not possible Enum based router

Open itsezc opened this issue 1 year ago • 4 comments

Problem

Perhaps, this is a limitation of my understanding of enums in Rust - if this is the case then this might be a simple docsite update, otherwise, when using the same "page" component across multiple routes, due to it being an enum, Rust is not a big of this. There might be use cases where the same component is used on multiple routes, perhaps with a variation.

Related:

  • #922

itsezc avatar Jan 14 '24 11:01 itsezc

You can have a different variant name and component name with this syntax #[route("path", ComponentName)]. That is currently only documented in the rust docs, but we could also document that syntax in the router reference in the guide

ealmloff avatar Jan 14 '24 15:01 ealmloff

I have a related issue of building a router with language prefixes such as

/en/blog/post/1
/es/blog/post/1
/de/blog/post/1

One workaround would be to wrap it with #[nest("/lang/:lang")] but that would not provide any restrictions to what lang can be.

And using component renaming is a bit of a no-go if there is no way to pass the static language name to the component.

ochrons avatar May 02 '24 20:05 ochrons

I worked around this problem with following route definition using the undocumented #[child] attribute and using a layout wrapper to store the language into context

pub enum BaseRoute {
    #[layout(LanguageWrapper)]
    #[child("/en")]
    RouteEnglish { child: LocalizedRoute },
    #[child("/fi")]
    RouteFinnish { child: LocalizedRoute },
    #[child("/ja")]
    RouteJapanese { child: LocalizedRoute },
    #[end_layout]
 }
 
pub enum LocalizedRoute {
    #[route("/recipe/:id")]
    RecipeView { id: String },
}

 #[component]
fn LanguageWrapper() -> Element {
    // extract language from route
    let route = use_route::<BaseRoute>().to_string();
    let lang = route.split('/').nth(1).unwrap_or("en");

    let language = lang.parse::<Language>().unwrap_or(Language::English);
    // set language to context
    use_context_provider(|| Signal::new(language.clone()));

    rsx! {
        Outlet::<BaseRoute> {}
    }
}

ochrons avatar May 02 '24 22:05 ochrons

With some further tweaking I was able to optimize it down to this, which works well for me.

#[derive(Clone, Debug, PartialEq, Routable)]
pub enum BaseRoute {
    #[nest("/:lang")]
    #[layout(LanguageWrapper)]
    #[child("")]
    RouteForLocalized { child: LocalizedRoute, lang: Language },
    #[end_layout]
    #[end_nest]
    #[route("/")]
    Home {},
    #[route("/:..route")]
    PageNotFound { route: Vec<String> },
}

// Routes that are localized to a specific language.
#[derive(Clone, Debug, PartialEq, Routable)]
pub enum LocalizedRoute {
    #[route("/recipe/:id")]
    RecipeView { id: String },
}

#[component]
fn LanguageWrapper(lang: Language) -> Element {
    // set language to context
    use_context_provider(|| Signal::new(lang.clone()));

    rsx! {
        Outlet::<BaseRoute> {}
    }
}

You just need to get everything exactly right through experimentation, because the macro error messages are typically not helpful at all.

Also, if passing language directly to the components, it simplifies to

#[derive(Clone, Debug, PartialEq, Routable)]
pub enum BaseRoute {
    #[nest("/:lang")]
    #[route("/recipe/:id")]
    RecipeView { lang: Language, id: String },
    #[end_nest]
    #[route("/")]
    Home {},
    #[route("/:..route")]
    PageNotFound { route: Vec<String> },
}

ochrons avatar May 03 '24 08:05 ochrons