Ruberoid
Ruberoid

Reputation: 186

Typescript assumes the property type to be T | Properties["key"] instead of just T

Say we have a type defined like so:

type PropsBase<T> = {
    optionalProp?: T;
} 

type Props<T, TAdditionalProps extends object> = PropsBase<T> & TAdditionalProps

Basically I have a common properties object and then child classes can add some more properties if they need them. Then I want to use it like that:

type State<T> = {
    value: T;
}

abstract class Base<T, TAdditional extends object = {}> {
    state: State<T>;
    constructor(props: Readonly<Props<T, TAdditional>>) {
        this.state = {
            value: props.optionalProp || this.fallbackValue() // ERROR!
        }
    }

    abstract fallbackValue(): T;
}

This causes the following error:

TS2322: Type 'T | Props<T, TAdditional>["optionalProp"]' is not assignable to type 'T'.
   'T' could be instantiated with an arbitrary type which could be unrelated to 
   'T | Props<T, TAdditional>["optionalProp"]'.

If I make optionalProp not optional (remove ?) then it works, it also works if I remove Readonly from constructor and just use Props<T, TAdditional> instead.

So my question is: why is this happening? and is there a way to still use Readonly here and have an optional property?

P.S. I have strict: true in my tsconfig.json. I know that if I set it to false it'll work.

Upvotes: 2

Views: 74

Answers (2)

samstronghammer
samstronghammer

Reputation: 58

I think switching from || to ?? fixes your problem.

Playground

type PropsBase<T> = {
    optionalProp?: T;
} 

type Props<T, TAdditionalProps extends object> = PropsBase<T> & TAdditionalProps

type State<T> = {
    value: T;
}

abstract class Base<T, TAdditional extends object = {}> {
    state: State<T>;
    constructor(props: Readonly<Props<T, TAdditional>>) {
        this.state = {
            value: props.optionalProp ?? this.fallbackValue()
        }
    }

    abstract fallbackValue(): T;
}

Upvotes: 2

Yes, strict mode doesn't work very well. But my reason to use it was strictBindCallApply and strictNullChecks. So my answer is – set property by property:

{
   "compilerOptions": {
       "strictBindCallApply": true,
       "strictNullChecks": true
   }
}

Upvotes: 0

Related Questions