DioxusLabs / dioxus

Fullstack app framework for web, desktop, mobile, and more.
https://dioxuslabs.com
Apache License 2.0
20.13k stars 771 forks source link

Multiple routes with the same component not possible Enum based router #1830

Closed itsezc closed 1 month ago

itsezc commented 7 months ago

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:

ealmloff commented 7 months ago

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

ochrons commented 4 months ago

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 commented 4 months ago

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 commented 4 months ago

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> },
}