Open janbuchar opened 1 year ago
Hey @janbuchar, could you please give an example with some context of the router functions/hooks you're using?
From what I understand, the params type would always be a Record<string, string>
as it's derived from the URL string. I don't know if it's useful in this case to use Number(params.param)
to cast it on a type/runtime level at the places require the usage of the param as a number.
Related to that, as I'm considering supporting type-safe search params - which similar to path param, derived from the URL and location state - would be a flexible type.
A concise example would be a route such as /articles/:articleId
. I would love to be able to say for instance
type PathParams {
articleId: number
}
somewhere in the page file and then call useParams("/articles/:articleId")
and get PathParams
from that.
Does this make sense?
Of course, doing something similar for search params would also be helpful. But if that was possible, I think it would be even more justified to want the same for path parameters - there is no fundamental difference between these two.
@janbuchar that's exactly what I thought about as well. But what I'm concerned about is on the runtime level all params would be strings, so declaring a param as number would be only on the type level.
As an example:
export type Params = { id: number }
const params = useParams('/invoice/:id')
// ^? { id: number }
typeof params.id === 'string'
I'm concerned about the potential mismatch here, please let me know what you think.
Maybe the search params can be handled differently if somehow values are decoded/encoded to preserve the types. I haven't tried something similar before.
I'm glad I'm not the only one who would consider this useful. I believe there'd have to be some runtime encoder/decoder for types like number
or Date
, and again, that applies to both path params and search params. Of course, plugging this into useParams
would probably only be possible with code generation.
@janbuchar the problem even if we have a route-based type for params and search params, the built-in useParams
and useSearchParams
wouldn't follow the types on the runtime level.
I believe the codegen would only make it easier, it can start as something similar to next-typesafe-url
for types and runtime validation. Let me know what you think.
I see. Yes, having runtime conversion of path or search parameters rules out using anything directly imported from react-router, tanstack/router and the like. Or, using those just forfeits the type safety added by the runtime conversion.
Having something like the package you linked is very close to what I had in mind 🙂
I love that I'm not the only one that was thinking about this. Personally, I use this snippet to address this problem, for me it's enough, but maybe for you it might become inspiration.
useParam.ts
import { useParams } from "react-router-dom"
function useParam<N extends boolean = false>(paramKey: string, numeric?: N): N extends true ? number : string {
const params = useParams()
const paramValue = params[paramKey]
if (paramValue == null) {
throw new Error(`Coundn't find param ${paramKey}. It should be declared in a route path.`)
}
if (numeric && isNaN(Number(paramValue))) {
throw new Error(`Param ${paramKey} is not number.`, { cause: { paramValue } })
}
return paramValue as N extends true ? number : string
}
export default useParam
When the error is thrown I catch it with ErrorBoundary
.
I use it to validate only numbers, but I guess you could spread this to Date also.
Is there a way to make url generation for path parameters such as
/blog/:postId
accept only numbers (provided thatpostId
should be a number) via typescript? Writing this kind of logic manually is super repetitive and I believe it should be possible to simplify it with code generation.