2

I've defined a generics Function, and I'm trying to pass the union of two specific sample of the generics to the function, like

function myCommonFunc<T>({
  data,
  render,
}: {
  data: T;
  render: (data: T) => number;
}) {
  return render(data);
}

interface TestX<T> {
  data: T;
  render: (data: T) => number;
}

let z: TestX<number> | TestX<string>;
// let z: TestX<number | string>  is wrong, because T will be number | string


if ((window as any).someUserInput === 1) {
  z = { data: 1, render: (a: number) => 1 };
} else {
  z = { data: '1', render: (a: string) => 1 };
}

// someother function

myCommonFunc(z);

and it throw errs.

I guess the reason is the function cannot infer type from the unionType, and If so, what should I do?

I know one solution is to use typeguard of z, like

function isTypeA(z: TestX<number> | TestX<string>): z is TestX<number> {
  return typeof z.data === 'number';
}
if (isTypeA(z)) {
  myCommonFunc(z);
}

but I think it actually change the structure of the program, and it need lots if-else to just for typescript, which is annoying. Can someone give me some other way to solve it?

==========

update:

François's solution is helpful, but somehow it cannot solve the real situation, actually z is a state in React Function Component, and myCommonFunc is a Component, and I'll update the real code:

interface TestX<T> {
  data: T;
  render: (data: T) => number;
}

function MyCommonFunc<T>({ data, render }: TestX<T>) {
  return <>{render(data)}</>;
}

type myType = TestX<number> | TestX<string>;

const F = () => {
  const [z, setZ] = useState<myType>({ data: 1, render: (a: number) => 1 });
  const [userInput, setUserInput] = useState<number>(1);
  useEffect(() => {
    if (userInput === 1) {
      setZ({ data: 1, render: (a: number) => 1 });
    } else {
      setZ({ data: '1', render: (a: string) => 1 });
    }
  }, [userInput]);
  // someOther Code
  return <MyCommonFunc {...z} />;
};

So I cannot just move the function call to the if-else block. and I'm so sorry that not put the real code at the first time.

1 Answer 1

3

This is a hard one. I see 3 solutions:

Solution 1

Solution 1 is the one you've mentioned above of using type guards:

const isTestNumber = (z: TestX<number> | TestX<string>): z is TestX<number> => {
  return typeof z.data === "number"
}

if (isTestNumber(z)) {
  myCommonFunc(z);
} else {
  myCommonFunc(z);
}

It is by far my least favorite solution.

Solution 2

Solution 2 is to cast z before passing it as a parameter. It looks like this:

myCommonFunc(z as TestX<any>); // ugly casting

I know it's ugly, but it's not particularly dangerous since the z variable is fully typed above. At least it does not alter the execution of the program.

Solution 3

Solution 3 requires some structural change to your program.

What you currently do is you declare the z variable, you assign it in an if-else statement and then you perform operations on it afterward.

What you could do is wrap all subsequent operations in another generic function.

It looks like this:

interface TestX<T> {
  data: T;
  render: (data: T) => number;
}

function myCommonFunc<T>(z: TestX<T>) {
  const { data, render } = z
  return render(data);
}

function allSubsequentOperations<T>(z: TestX<T>) {
  // someother function
  
  myCommonFunc(z);
}

if ((window as any).someUserInput === 1) {
  const z: TestX<number> = { data: 1, render: (a: number) => 1 };
  allSubsequentOperations(z)
} else {
  const z: TestX<string> = { data: "1", render: (a: string) => 1 };
  allSubsequentOperations(z)
}

This is what I usually do when dealing with this kind of problem. From the moment z is assigned, your whole program becomes generic.

I hope you find these answers helpful,

François

Sign up to request clarification or add additional context in comments.

1 Comment

thanks for your help,and solution 2 is somehow solve my problem! But we both know it's not the perfect solution; and I've considerd solution 3, but actually the real code is in a react component, and z is a state, so solution 3 is not properly in this situation, I'll append the real code, and thanks for your solution again!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.