Is there a possibility to specify length of array that comes from props?

Viewed 125

Is it possible to specify the length of the array that comes from props using type from Typescript?

We have an array of objects:

const arrayPassedViaProps = [
  { key: '1', label: 'Label 1', value: 9 },
  { key: '2', label: 'Label 2', value: 3 },
  { key: '3', label: 'Label 3', value: 3 },
  { key: '4', label: 'Label 4', value: 22 },
  { key: '5', label: 'Label 5', value: 1 }
]

I pass this array as props like this:

<DownlineProgress data={arrayPassedViaProps} />

Inside DownlineProgress component I defined a type:

type DownlineProgressBarProps = {
  data: {
    key: string
    label: string
    value: number
  }[]
}

I'm using this type with props

export const DownlineProgress = ({data}: DownlineProgressBarProps) => ... 

I would like to be informed by Typescript when I'm passing props that the array doesn't have the correct length, it should contain TWO or THREE objects

How I can modify my type to achieve such behavior?

PS. currently I'm using if statement to check

if(data.length > 3 || data.length < 2){
  throw(`DownlineProgress need an array of length 2 or 3. You pass ${data.length}`)
}
3 Answers

Here is a pure TypeScript solution:

interface Data {
    key: string;
    label: string;
    value: number;
}

// The following type should have two or three elements of type Data
type ArrayWithTwoOrThreeDataElements = [ Data, Data, Data? ];

In your case since in the props value you are expecting an object with a property named data of type Data, you can define the type of props as follow:

type DownlineProgressBarProps = [
    { data: Data }, 
    { data: Data },
    { data: Data }?
];

Naturally you can create a type for a single prop item if you want to avoid repeating the data property key:

interface Data {
    key: string;
    label: string;
    value: number;
}

interface DownlineProgressBarProp {
    data: Data
}

type DownlineProgressBarProps = [
    DownlineProgressBarProp,
    DownlineProgressBarProp,
    DownlineProgressBarProp?
];

You can specify the length of an array with these custom types:

type TupleOf<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
type _TupleOf<T, N extends number, R extends unknown[]> = R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;

And then:

type DownlineProgressBarProps = {
  data: TupleOf<{
    key: string
    label: string
    value: number
  }, 2 | 3>
}

You can create a custom function for the same it would be the correct way.

  const propTypes = {
    data: arrayOfLength.bind(null, 2)
  }

  const arrayOfLength = (expectedLength, props, propName, componentName) => {
    const arrayPropLength = props[propName].length

    if (arrayPropLength !== expectedLength) {
      return new Error(
        `Invalid array length ${arrayPropLength} (expected ${expectedLength}) for prop ${propName} supplied to ${componentName}. Validation failed.`
      )
    }
  }
Related