Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

TypeScript: Restrict function parameter value to values of array within dynamic variable

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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!

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading