leptos
leptos copied to clipboard
Type safe search params in router
Is your feature request related to a problem? Please describe. It is often recommended to use search params as state, however it is often difficult to pull off in big systems because the link tag don't know what search params the route expects. I ran across tanstack router which, among other great ideas, has type safe search params. So I was wondering if leptos could add support for something similar.
Describe the solution you'd like Looking at it, the tanstack router is quite a lot different from the leptos router, so I can't see immediately how the a-tag would know what search params the route would expect. But ideally the route would describe the expected type, perhaps just a struct that impl Deserialize, and then the a-tag would know the expected type.
Something like
#[derive(Deserialize)]
struct UsersSearchParams {
tab: Tab
}
// route
<Route path="/users" view=Users search=UsersSearchParams/>
// later when linking to the page
<A href="/users" search=UsersSearchParams {tab: Tab::Settings} />
Describe alternatives you've considered The current solution is okay, as that is what the vast majority of routers out there use, but it would be cool to get type safe routes like this
So, first: agreed that this would be amazing. I would genuinely love to have it available, so read everything below as "hm, how could we get the best equivalent experience."
As I understand it, TanStack Router supports autocompletion/type checking for both route params (segments of the URL, like /users
) and search/query params (?tab=...
)
Route Params
The core issue here, I think, is that Typescript's type system is designed for autocompletion and IDE experience, while Rust's type system is designed for memory layout. One downstream effect of this is that Typescript's type system supports string literal types. This means that "users"
is itself a type, which means that it can be used for things like link autocompletion, because the set of valid URLs can actually be typed.
Rust doesn't support string types. There is some early-stage support for const generic &'static str
, which is cool, but it's nightly only and I don't think it's actually fit for this purpose. "users"
is just of type &'static str
, not type "users"
, so we can't use type-checking to ensure it's valid.
Rust's enums function in essentially the same way that a union of Typescript param types could function here. i.e., functionally speaking, and approximation of type-checking for route params can be achieved by something like
// some derive macro for `Display` here
enum Routes {
Home,
Users,
About
}
<Route path=Routes::Users view=Users /* etc */ />
<A href=Routes::Users /* might need a .to_string(), I forget */>
The autocomplete part here is harder, because the case above doesn't know that it should be some Routes
variant, you can always give it href="foo"
. I guess a custom <Link/>
component that only takes some combination of your app's routes?
Search/Query Params
I can imagine an API design that is something like this.
- Extend
<Route/>
matching so that you can specify asearch
prop, which would somehow specify "only match this route if a struct ofTab
can be non-exhaustively deserialized from the search query" - Some kind of a trait relationship combining a single variant in the
Routes
enum with a single search query struct type. This is hard actually because enum variants aren't their own types. So the above may need to change to reflect that, where you have somePossibleRoutes
trait that<Link href/>
is generic over, and a link between the path type and the query struct type. - Add a
search
prop onto the custom<Link/>
above, where the route and the query need to match.
I've been slowly working away at a larger router rewrite so I'll take this all into consideration, but... there's a reason TanStack Router is the first and only one to support this level of type safety, and has been designed entirely around it.
The yew router has some good ideas in this regard. It doesn't have type-safe search params, but it does have type safe query params. And adding type safe search params to the api wouldn't be that difficult I think.
Perhaps something like this
#[derive(Deserialize, Serialize)]
enum PostTab {
Comments,
SimilarPosts
}
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/post/:id")]
Post { id: i32, tab: Option<PostTab> }, // Since the `tab` field is not used in the path, it is used in a search param
}
// This is yew's way of doing it, and don't map that well to leptos. I have no idea what nested routing would look with this
fn switch(route: Route) -> Html {
match route {
Route::Home => view! { <h1>"Home"</h1> },
Route::Post { id, tab } => view! {<p>"You are looking at Post" {id} "on tab" {tab}</p>},
}
}
// Inside a component
<Link<Route> to={Route::Post {id: 1, tab: Some(PostTab::Comments)}}>...</Link<Route>>
// Would link to /post/1?tab=Comments
However some care would need to be taken if the tab doesn't deserialize correctly. Perhaps just wrapping it in a Result would suffice.
Also, as I write, I can't see how this would map well to nested routes