I have an abstract class with default state values that are typed inside ResourceState
interface. I am getting a following error when I try to assign my extended State
to the state property of my abstract class. I tried many different ways using intersection, though it still doesn't work. What I would like to achieve is, that I can provide a generic interface to the state property so that I can add more values apart from the default ones.
This is the error I got:
Type '{ items: never[]; isLoading: false; error: string; }' is not assignable to type 'State'. '{ items: never[]; isLoading: false; error: string; }' is assignable to the constraint of type 'State', but 'State' could be instantiated with a different subtype of constraint 'ResourceState'.
This is my code:
export interface BaseItem {
id: string;
name: string;
}
export interface ResourceState < Item extends BaseItem > {
items: Item[];
selectedItem ? : Item;
isLoading: boolean;
error: string;
}
export abstract class Resource < Item extends BaseItem, State extends ResourceState < Item > , Mutations, Getters, Actions > {
state: State = { // this is where the error occurs
items: [],
isLoading: false,
error: '',
}
}
When you say State extends ResourceState<Item>
you are saying that State
needs to conform to ResourceState<Item>
but because this is a generic type parameter, it may not be exactly a ResourceState<Item>
.
Imagine this:
interface MyState extends ResourceState<{ a: 123 }> {
myCustomRequiredProperty: number
}
Now you pass MyState
as State
to your class, then you contruct state with:
state: State = { // this is where the error occurs
items: [],
isLoading: false,
error: '',
}
Then your state
variable would not be constructed with the myCustomRequiredProperty
property, which is required.
This is the error that typescript is (cryptically) trying telling you.
If you are constructing the state variable in the Resource
class, then the State
should not be generic because your executable code in this class can't know all properties of the types that could be generically passed in. So State
should not extend ResourceState<Item>
, it should be exactly a ResourceState<Item>
.
That would look like this:
export abstract class Resource<Item extends BaseItem> {
state: ResourceState<Item> = {
items: [],
isLoading: false,
error: '',
}
}
However, if you want the ability to add properties to state, then you can't do that without some initial values. You need to somehow initialize those unknown properties. To do that, you would use your approach above, but with a constructor that accepts whatever type is the full state and then fill in the unknown properties of that type with an object that has that info.
export abstract class Resource<
Item extends BaseItem,
State extends ResourceState<Item>
> {
state: State
constructor(initialState: State) {
this.state = {
...initialState, // Fills in extended properties
items: [],
isLoading: false,
error: '',
}
}
}