amankkg
amankkg

Reputation: 5061

Generic type inferred from wrong usage

As I understand, TS prefers direct type usages over "boxed" ones (e.g. in function) to infer a generic type from.

type Props<O, V> = {
  options: O[]
  getOptionValue: (o: O) => V
  value?: V
  onChange?: (v: V, o: O) => void
}

In this snippet type V is inferred from value field which is undesirable since value property is optional. Can I somehow force generic type inference to rely on getOptionValue property?

Sandbox example where if no value passed to a component its onChange argument v becomes unknown

Upvotes: 1

Views: 65

Answers (2)

jcalz
jcalz

Reputation: 328142

I'm somewhat inclined to close this as a duplicate of this question. The compiler doesn't make multiple inference passes when trying to infer the types of a single object.

My workaround suggestion here would be the same as there: split your function into multiple parameters instead of a single parameter with multiple properties. You could even make a helper function like this:

const props = <O, V>(
    options: O[],
    getOptionValue: (o: O) => V,
    value?: V,
    onChange?: (v: V, o: O) => void
): Props<O, V> => ({ options, getOptionValue, onChange })

And then use it:

const theProps = props(
    [""],
    (o) => "test",
    undefined,
    (v, o) => v.length < o.length
);

Here you can see that O and V are properly inferred to be string and v is not unknown in the onChange callback. You can use also this with JSX via spread:

const c = <C {...theProps} />;

It's not perfect, since you're getting inference in exchange for a multi-step jsx element creation. But it does work.

Playground link to code

Upvotes: 2

Rubydesic
Rubydesic

Reputation: 3476

I don't think it's possible, because even if you remove the value property entirely typescript still won't use getOptionValue to infer the type.

function C<O, V>(props: {
    options: O[]
    getOptionValue: (o: O) => V
    onChange?: (v: V, o: O) => void
}) {}

C({
    options: [""],
    getOptionValue: (o) => "test",
    onChange: (v, o) => v.test // error: v is STILL 'unknown'
})

Upvotes: 1

Related Questions