Replies: 6 comments 1 reply
-
Thanks, I implemented it slightly different so that a) only need to pass in the names as props, not as types, and b) not do the re-assignment since I don't mind if there are some other (optional) parameters: import { useParams } from 'react-router-dom';
type RequiredParams<Key extends string = string> = {
readonly [key in Key]: string;
};
export const useRequiredParams = <Params extends string>(
requiredParamNames: Params[],
): RequiredParams<typeof requiredParamNames[number]> => {
const routeParams = useParams();
for (const paramName of requiredParamNames) {
const parameter = routeParams[paramName];
if (!parameter) {
throw new Error(
`This component should not be rendered on a route which does not have the ${paramName} parameter`,
);
}
}
return routeParams as RequiredParams<typeof requiredParamNames[number]>; // after looping through all required params, we can safely cast to the required type
}; |
Beta Was this translation helpful? Give feedback.
-
I wanted to share my take as well, since we heavily use static-path in my company. const useSafeParams = <Pattern extends string>(
path: Path<Pattern>
): Record<PathParamNames<Pattern>, string> => {
const routerParams = useParams();
return path.parts.reduce((currentParams, staticParam) => {
if (staticParam.kind === "param") {
const paramValue = routerParams[staticParam.paramName];
if (paramValue === undefined) {
/** Do something about it :) */
} else {
return {
...currentParams,
[staticParam.paramName]: paramValue
};
}
}
return currentParams;
/**
* We are kinda lying to TypeScript here, but the thing is that if you pass a path with no parameters to `useParams`,
* you will get a type error and therefore, this would never return an empty object (you can always bypass TypeScript, but should you?).
*/
}, {} as Record<PathParamNames<Pattern>, string>);
}; Usage would be something like const moviePath = path("/movies/:movieId/comments/:commentId");
/** Nicely typed and safe! */
const { movieId, commentId } = useSafeParams(moviePath);
/** Whoops, you get a type error! seasonId does not exist in type { movieId: string, commentId: string } */
const { seasonId } = useSafeParams(moviePath); |
Beta Was this translation helpful? Give feedback.
-
Just sharing my case: Add type guard with predicate
type RouteParams = { [K in string]?: string };
type RequiredParams<Key extends string> = {
readonly [key in Key]: string;
} & Partial<Record<Exclude<string, Key>, string>>;
const hasRequiredParams = <T extends string>(
params: RouteParams,
requiredParamNames: readonly T[]
): params is RequiredParams<T> =>
requiredParamNames.every((paramName) => params[paramName] !== null && params[paramName] !== undefined);
const useRequiredParams = <T extends string>(requiredParamNames: readonly T[]): Readonly<RequiredParams<T>> => {
const routeParams = useParams<RouteParams>();
if (!hasRequiredParams(routeParams, requiredParamNames)) {
throw new Error(
[
`This component should not be rendered on a route since parameter is missing.`,
`- Required parameters: ${requiredParamNames.join(", ")}`,
`- Provided parameters: ${JSON.stringify(routeParams)}`,
].join("\n")
);
}
return routeParams;
}; // requiredParam: string
// someOtherParam: string | undefined -> defined by react-router
const { requiredParam, someOtherParam } = useRequiredParams(["requiredParam"]);
// managing requiredParamNames as array constant requires `as const`
// since if typeof requiredParamNames is string[], return type of useRequiredParams will be `RequiredParams<string>`
// which is why typeof requiredParamNames param is `readonly T[]` in useRequiredParams
const requiredParamNames = ["id", "name"] as const; // readonly ["id", "name"]
const { id, name } = useRequiredParams(requiredParamNames); |
Beta Was this translation helpful? Give feedback.
-
Would love to see something like this. any plans for it? |
Beta Was this translation helpful? Give feedback.
-
I'm going to convert this to a discussion so it can go through our new Open Development process. Please upvote the new Proposal if you'd like to see this considered! |
Beta Was this translation helpful? Give feedback.
-
Upvote! My application relies heavily on route state for better user experience when sharing links. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
What is the new or updated feature that you are suggesting?
Export a useRequiredParams hook, which accepts a list of the route parameters required by a component, throws an error if they are not set, and returns parameter variables with type
string
.Why should this feature be included?
The base
useParams
hook returns param variables with typestring | undefined
. This is the correct type definition, but commonly users of this hook are using it in a component they have no intention of rendering on a route without the route parameter in question. Thestring | undefined
type causes (for valid reasons) friction in the dev experience. There is discussion of this here.On one of my projects at work this was bothering me and I put together a useRequiredParams hook which checks if they are set, throws a useful error if not, and returns params with type
string
.I think it would be helpful to include this alongside the base useParams hook (and just document that it will throw if a required param is unset).
I wrote up my own version here, but maybe a better implementation is possible. The point is that it throws a helpful error, and the hook name makes the intent explicit in contrast to
useParams
.Beta Was this translation helpful? Give feedback.
All reactions