skip to Main Content

I have a fully working example located here:

https://playcode.io/1548464

The code works, but TypeScript is complaining. Unfortunately I have run out of ideas of how I can make the code properly type safe. I have scoped the web for numerous examples however I haven’t found one that has my use case unfortunately.

I would like the code to accept a generic type and an array of said generic type and for the code to understand that. also at the same time providing those values to child components via the context provider.

Some of the errors.

1 (line 35):

Type '((activeIdOrIds: T[]) => void) | ((activeIdOrIds: T) => void)' is not assignable to type '((activeIdOrIds: Id[]) => void) | ((activeIdOrIds: Id) => void)'.
  Type '(activeIdOrIds: T[]) => void' is not assignable to type '((activeIdOrIds: Id[]) => void) | ((activeIdOrIds: Id) => void)'.
    Type '(activeIdOrIds: T[]) => void' is not assignable to type '(activeIdOrIds: Id[]) => void'.
      Types of parameters 'activeIdOrIds' and 'activeIdOrIds' are incompatible.
        Type 'Id[]' is not assignable to type 'T[]'.
          Type 'Id' is not assignable to type 'T'.
            'Id' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Id'.
              Type 'null' is not assignable to type 'T'.
                'null' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Id'.ts(2322)

2 (line 66):

Type 'Id[]' is not assignable to type 'T[]'.
  Type 'Id' is not assignable to type 'T'.
    'Id' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Id'.
      Type 'null' is not assignable to type 'T'.
        'null' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Id'.ts(2322)

3 (line 68):

Argument of type 'T[]' is not assignable to parameter of type 'Id[] & Id'.
  Type 'T[]' is not assignable to type 'Id[] & number'.
    Type 'T[]' is not assignable to type 'number'.ts(2345)

As you see my generics seem to be at issue with the constraints of Id.

2

Answers


  1. The reason is that your specify type itself is too absurd, see the following example:

    const TestyContext = createTestyContext();
    
    function Testy<T extends Id>(props: TestyProps<T>) {
      return (
        <div>
          <TestyContext.Provider
            value={{
              multiple: props.multiple,
              onChange: props.onChange,
              activeIdOrIds: props.activeIdOrIds,
            }}
          >
            {props.children}
          </TestyContext.Provider>
        </div>
      );
    }
    

    now you initialize createTestyContext without parameter T initially it will be unknown but when rendering specifying for props type T is being specified as T extends Id so these 2 types can’t match T is definitely not T extends Id

    now you have to specify T as T or T as any or like this:

    function Testy<T extends Id>(props: TestyProps<T>) {
      const TestyContext = createTestyContext<T>();
    }
    

    or like this:

    const TestyContext = createTestyContext<any>();
    
    function Testy<T extends Id>(props: TestyProps<T>) {
      // ...
    }
    
    Login or Signup to reply.
  2. So, the short explanation for why typescript is yelling is because your types don’t respect generics covariance. This is because Typescript is concerned with statically checking that types will always be compatible, no matter what you feed it.

    A quick example to show how your types could fail (using animals instead of ids for simplicity)

    Say you have the following

    const a: Animal[...cats, ...dogs, ...mice]
    const c: Cat[] = [...cats];
    const d: Dog[] = [...dogs];
    const m: Mice[] = [...mice];
    
    const z: (T extends Animal)[] = m;
    const r = (animals: Animal[]) {
        animals.push(monkey[0]);
    }
    r(z) // ?
    

    Typescript can’t tell how an object is going to get used at runtime, so needs to verify there is no edge case where the types used can be violated. So it has to check strictly that when assigning different types, that one is a complete subset of another.

    As for your code itself, I would refactor it down to only use the type OneOrMore = T | T[]. The case of single of multiple is handled by [id], and you know if multiple are allowed based on if it is an array or not. Everything else can be safely extrapolated from there.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search