-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Params
and related functions generic
#8019
Conversation
The main reason I've resisted making <Switch>
<Route path="/teams/:team/apps/:app" component={PageHeader} />
<Route path="/teams/:team" component={PageHeader} />
<Route component={PageHeader} />
</Switch> I show a breadcrumb menu at the top of the page depending on whether you're in the team, app, or neither context of the site. That makes the With this system for generics, there is no way for me to represent that typing. I would have to override it with a type assertion. interface TeamAppParams {
team?: string
app?: string
}
const { team, app } = useParams() as TeamAppParams I think my core point is you might not always know the Route definition at your call site, which makes this a dangerous type assertion and could easily lead to hard-to-find bugs. |
Good points and I don't necessarily disagree, which I why I think it ought to default to That said, I think you make a good case for the value being potentially undefined here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! 👍
@@ -11,7 +11,7 @@ import { | |||
describe("useParams", () => { | |||
describe("when the route isn't matched", () => { | |||
it("returns an empty object", () => { | |||
let params: Record<string, string> = {}; | |||
let params: Record<string, string | undefined> = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we reuse the Params
type in this file instead of declaring our own Record
type here? Not a big deal, just curious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah totally, will make a TODO for this.
If const { id } = useParams<'id'>();
doSomething(id); // typescript error, id can be null
doSomething(id!); // workaround is this, everywhere I would rather expect a syntax like interface MyRouteParams {
id: string
};
const { id } = useParams<MyRouteParams>();
doSomething(id); // id cannot be null This takes into account that as a user, I should know what comes in my route and what can and cannot be undefined. |
The type of |
I don't see how that is different from any other serialized form, like reading values from a database or deserializing JSON. Required data may or may not be there, and there is always friction between what the code expects and the restrictions set by the underlying storage system (in this case, the URL). No objections to the type argument, but can be solved with a supertype: interface Params {
[key: string]: string | undefined
}
interface MyParams extends Params {
foo: string,
bar: string | undefined,
baz: int // error
} |
How would you deal with this now?
Since hooks can't be called conditionally, I can't render something else. The only option I see here is to split it into multiple components, which would drive my crazy 🙈 |
I just want to add that I totally understand why you made it <Route path="/projects">
<Route path="new" element={<ProjectNew />} />
<Route path=":slug" element={<Project />} />
</Route>
...
function Project() {
const { slug } = useParams()
const [{ data }] = useQueryData({ variables: { slug } })
return <div>Project title: {data.title}</div>
} Since I know that I do not use Project as a component which can be placed everywhere, I also know that |
It depends on your hook, of course, but things like SWR get around this by accepting null params as a noop: useSWR(slug ? `/api/projects/${slug}` : null) In your case, you could do one of two things: const [{ data }] = useQueryData({ variables: { slug: slug! } })
// or
const [{ data }] = useQueryData(slug ? { variables: { slug } } : {}) Depends on what is more idiomatic for you. |
I've created typed |
You could also do I suppose one thing we could do is add a second type parameter for the value type. That wouldn't stop folks from doing dumb things like |
That would be great 😀 I would be happy if you could think about this option. I did |
The
Params
type and functions that consume it should be generic to allow simpler static typing for query params.Note, the generic type defaults to
string
so this doesn't break anyone's existing code, but it will enhance it for those looking for added type awareness.Resolves #7978