Home > Net >  Return a Type error if prop passed is out of range
Return a Type error if prop passed is out of range

Time:10-24

I have a component which takes in two props; children(react elements) and index. When index is passed, it should return the child element at that particular index. I would like to modify my types to return a type error incase the index passed is out of range ie if I have a total of 6 elements and index passed is 40. I would like typescript to showing me that I cannot input that index.... in the Main component.


import React from 'react';
type IComp = {
    children: React.ReactElement[],
    index: number
}

function Comp(props: IComp) {
    return (
        <div >
            {props.children.map((item) => {
                return (
                    <button></button>
                )
            })}
        </div>
    )
}

function Main() {
    return (
        <Comp index={20}>
            <div>
                <p>One</p>
            </div>
            <div >
                <p>Two</p>
            </div>
            <div>
                <p>Three</p>
            </div>
        </Comp >
    );
}


CodePudding user response:

In order for this to work, your Comp component needs to keep track of the exact length of the children array, so that it can compare index to it. That means the children component will need to be something like a tuple type instead of an arbitrary array type, and IComp will need to be generic in that tuple type.

Here's one approach:

type IComp<T extends React.ReactElement[]> = {
    children: [...T],
    index: StrToNum<`${number}` & keyof T>
}

type StrToNum<T> = T extends `${infer N extends number}` ? N : never

So children is of generic type T which is constrained to ReactElement[]. Actually it's the variadic tuple type [...T], which is essentially the same as T except it gives the compiler a hint that it should infer a tuple type instead of just an array type when it sees an array in that position.

And index is going to be a union of all the applicable numeric literal types corresponding to valid indices of the T tuple. First, if you inspect keyof T (using the keyof type operator) where T is a tuple, you'll see a bunch of array method member names, plus string versions of the valid numeric indices:

type ExampleTuple = [Element, Element, Element];

type ExampleTupleKeys = keyof ExampleTuple;
/* type ExampleTupleKeys = number | typeof Symbol.iterator | typeof Symbol.unscopables | 
  "0" | "1" | "2" | 
  "length" | "toString" | "toLocaleString" | "pop" | "push" | 
  "concat" | "join" | "reverse" | "shift" | 
  "slice" | "sort" | "splice" | "unshift" | "indexOf" | 
  "lastIndexOf" | "every" | "some" | "forEach" | 
  "map" | "filter" | "reduce" | "reduceRight" | "find" | 
  "findIndex" | "fill" | "copyWithin" | 
  "entries" | "keys" | "values" | "includes" */

That "0" | "1" | "2" is the part we care about. To get rid of everything else, we can intersect those keys with `${number}`, a template literal type roughly corresponding to "all numeric strings" (more or less):

type NumericStringExampleTupleKeys = keyof ExampleTuple & `${number}`
// type NumericStringExampleTupleKeys = "0" | "1" | "2"

So this is closer to what we want, but you don't want to use strings like "1", you want numbers like 1. Which means we need to convert string literal types to number literal types. This can now be done directly with TypeScript 4.8's improved inference in template literal types, allowing us to write the StrToNum<T> utility type above:

type NumericExampleTupleKeys = StrToNum<keyof ExampleTuple & `${number}`>;
// type NumericExampleTupleKeys = 0 | 1 | 2

And there you go, the set of acceptable numeric indices to T.


So you want Comp to be generic in T just like IComp:

function Comp<T extends React.ReactElement[]>(props: IComp<T>): JSX.Element {
  /* impl */
}

And now we can test it:

// okay
<Comp index={2}>
    <div><p>One</p></div><div><p>Two</p></div>
    <div><p>Three</p></div>
</Comp >

// error! Type '3' is not assignable to type '0 | 1 | 2'
<Comp index={3}>
    <div><p>One</p></div><div><p>Two</p></div>
    <div><p>Three</p></div>
</Comp >

// okay
<Comp index={3}>
    <div><p>One</p></div><div><p>Two</p></div>
    <div><p>Three</p></div><div><p>Four</p></div>
</Comp >

The compiler detects that 2 is a valid index when Comp has three children, and that 3 is an invalid index when Comp has three children, but a valid index when Comp has four. Looks good!

Playground link to code

  • Related