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

How to specify conditional types for React components props?

Say I have an object defining my Typography components color and shade.

const theme = {
  primary: [10, 20, 50, 100],
  secondary: [20, 30, 80],
  accent: [10, 20]
}

Typography should accept 2 props, color and shade.

Type definition for color can be: keyof typeof theme.

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

How do I specify the type of shade prop such that it only accepts the value within the array of the palette defined in the theme object? For instance, if color is accent, shade cannot be 30. It can only be 10 or 20.

Code snippet:

function Typography({
  color,
  shade,
  children,
}: {
  color: keyof typeof theme;
  shade: ???;
  children: React.ReactNode;
}) {
  return <div>{children}</div>;
}

function Main() {
  return (
    <Typography 
      color="primary" 
      shade={}>
        Some text
    </Typography>
  );
}

Codesandbox – https://codesandbox.io/s/conditional-prop-types-cwy49q

>Solution :

There are two possible approaches:

  1. Using generics
  2. Create a union of all possibilities (not recommended for big amount of values)

Either of the approaches will require a change in theme.

Currently, the type of the theme is Record<string, number[]> which is not suitable for us. We have to prevent the compiler from widening types to their primitives.

This can be done by using const assertion:

const theme = {
  primary: [10, 20, 50, 100],
  secondary: [20, 30, 80],
  accent: [10, 20],
} as const;

The problem with const assertion is that we lose type checking and if you use Typescript >= 4.9 it can be fixed with satisfies operator:

const theme = {
  primary: [10, 20, 50, 100],
  secondary: [20, 30, 80],
  accent: [10, 20],
} as const satisfies Record<string, readonly number[]>;

Since const assertion converts everything to readonly it is crucial to write readonly number[] in satisfies instead of just number[].

Let’s create a type for the theme:

// type Theme = {
//     readonly primary: readonly [10, 20, 50, 100];
//     readonly secondary: readonly [20, 30, 80];
//     readonly accent: readonly [10, 20];
// }
type Theme = typeof theme;

Approach with generics:

Typography will accept a generic parameter constrained by the keyof Theme and by using it we will get the values for shade using indexed access:

function Typography<T extends keyof Theme>({
  color,
  shade,
  children,
}: {
  color: T;
  shade: Theme[T][number];
  children: React.ReactNode;
}) {
  return <div>{children}</div>;
}

Approach with the union of possible values:

By using mapped types we will map through Theme and generate a union of {color: string, shade: number}:

  type PossibleValuesUnion = {
    [K in keyof Theme]: { color: K, shade: Theme[K][number] }
  }[keyof Theme]

Usage:

 function Typography({
    color,
    shade,
    children,
  }: PossibleValuesUnion & {
    children: React.ReactNode;
  }) {
    return <div>{children}</div>;
  }

Either of these should do the trick, personally, I would recommend the first approach if the theme will grow bigger.

playground

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