Issue
I have a simple react hook that returns a function. I would like this function to only accept values from an array that gets passed into the hook.
Heres the hook:
type Options<P> = {
pathname: string;
rootPath: string;
paths: P;
};
export const useLayoutPaths = <P extends string[]>(options: Options<P>) => {
const { pathname, rootPath, paths } = options;
if (paths.length === 0) {
throw new Error("paths must be a non-empty array.");
}
const currentPath = pathname.substring(pathname.lastIndexOf("/") + 1);
const value = paths.includes(currentPath) ? currentPath : paths[0];
const getPath = (name: typeof paths[number]): string =>
`${rootPath}/${String(name)}`;
return { value, getPath };
};
I would like the getPath function to only allow values that exist within the "paths" variable.
Heres my current usage:
const { value, getPath } = useLayoutPaths({
pathname,
rootPath: `/department/${orgId}`,
paths: ["trends", "comments"],
});
console.log(getPath("trends")) <-- Only allow "trends" or "comments"
Solution
You want the compiler to keep track of the literal type of the strings passed in as the paths property. Unfortunately this did not happen with a generic constraint like P extends string[]. One way to increase the likelihood that the compiler will treat "foo" as being of type "foo" instead of type string is to constrain a generic type parameter to string. So P extends string will work better. We can just have P be the type of the elements of paths instead of paths itself, like this:
export const useLayoutPaths = <P extends string>(options: Options<P[]>) => {
// impl
};
This will work as desired, but the compiler doesn't like letting you look up a string in an array of P. See this question and answer for more information. The easiest way to deal with that is to widen paths from P[] to readonly string[] before using includes():
export const useLayoutPaths = <P extends string>(options: Options<P[]>) => {
const { pathname, rootPath, paths } = options;
if (paths.length === 0) {
throw new Error("paths must be a non-empty array.");
}
const currentPath = pathname.substring(pathname.lastIndexOf("/") + 1);
const value = (paths as readonly string[]).includes(currentPath) ? currentPath : paths[0];
const getPath = (name: typeof paths[number]): string =>
`${rootPath}/${String(name)}`;
return { value, getPath };
};
And now things work as you want:
console.log(getPath("trends")) // okay
getPath("oops"); // error!
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.