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"
CodePudding user response:
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!