1

I've encountered a problem regarding typescript and need some help

I'm new to typescript and a code block below illustrates shortened version of my problem.

Link to Typescript Playground

interface A  {
    value1: string;
    value2: string;
    func(a: string, b: string): void;
}

interface B {
    value1: number;
    value2: number;
    func(a: number, b: number): void; 
}


function Outer <T extends A | B>(myFunc: T['func'], a: T['value1'], b: T['value2'])  {
    myFunc(a, b); // get an error: Argument of type 'string | number' is not assignable to parameter of type 'never'.
}

What I want is to pass parameters to 'Outer' function which will then infer 'T'.

I thought it would be okay to call 'myFunc', but error comes up and says I can't assign 'a' and 'b' to 'never'.

** This set of codes might look a bit weird. It's actually intended to be a react hook.

What's going on here? and What would be a proper way to solve this?

Thanks a lot in advance.

Jackson
  • 33
  • 1
  • 4

2 Answers2

1

This is because of union to intersection behavior of typescript. Normally when the function Outer is called, it is not clear, what object type(A or B) is passed to it, so typescript considers the parameters of the func function in the form of the intersected types.

// it is not obvious that myFunc is this ? :
func(a: string, b: string): void;


//or this? :
func(a: number, b: number): void;


// so typescript changes it like this :
func(a:number & string, b: number & string) = func(a:never, b:never)

//since number set and string set do not have any intersection, the types are never.

What you can do is to consider separate types for the properties of each interface so that their intersection is an existing type.

type AValue =  {a1:string; a2:string}

interface A {
    value: AValue;
    func(x:AValue): void; 
}

type BValue =  {b1:number; b2:number}

interface B {
    value: BValue;
    func(x:BValue): void; 
}


function Outer <T extends A | B>(myFunc: T['func'], a: AValue & BValue)  {
    myFunc(a);
}

You can read more about union to intersection in typescript here. Although it might be better to change the Outer function declaration and solve the problem in a different way so that this union to intersecton thing does not happen.

mahooresorkh
  • 1,361
  • 2
  • 8
  • 16
  • but now OP what have to pass an object to `a` containing `a1`, `a2` *and* `b1`, `b2` which is probably not what he had in mind. – Tobias S. Oct 20 '22 at 06:57
  • It can be something like this `{a1:"foo", a2:"bar", b1:0, b2:0}`. But you're right. Always two useless props, either (`a1` and `a2`) or (`b1` and `b2`) should be passed to the function. I wanted to explain the mechanism of the issue. As I said it might be better to change the declaration of `Outer` or interfaces and raise the issue in another way. – mahooresorkh Oct 20 '22 at 07:10
  • Thanks a lot for detailed explanation. You got me in right direction. – Jackson Oct 20 '22 at 07:57
0

This particular example can be solved in a more elegant way

Typescritp playground

function Outer<T>(myFunc: (a: T, b: T) => void, a: T, b: T)  {
    myFunc(a, b);
}

// example calls
Outer<number>((a,b) => { console.log( `a+b= ${a+b}` )},  6, 8);

Outer<string>((a,b) => { console.log( `a=${a} b=${b}` )},  "aa", "bbb");

// You can eve omit the T, since it will imply it for the values of 2nd nad the third value
// In this cast it is a date
Outer((a,b) => { console.log( `a=${a.toLocaleString()} b=${b.toLocaleDateString()}}` )},  new Date(), new Date("2017-01-01"));
Svetoslav Petkov
  • 1,117
  • 5
  • 13