Reputation: 57964
I'm trying to wrap my head around why TypeScript reports that my condition will always be false due to no type overlap between Action.UP | Action.DOWN
and Action.LEFT
in this situation (playground link):
class Component<S> {
public state: S;
public render() {}
}
enum Action {
UP,
DOWN,
LEFT,
RIGHT,
}
interface State {
action: Action;
}
const initialAction: Action.UP | Action.DOWN = Action.UP;
class MyComponent extends Component<State> {
public state = {
action: initialAction,
}
public render() {
// Why is action not of type Action as declared in the interface?
const isLateral = this.state.action === Action.LEFT;
}
}
If this.state.action
only has an initial value of type Action.UP | Action.DOWN
, but is declared as type Action
in the interface, why can't I compare it to Action.LEFT
? If I can reassign this.state.action
in the future to be either LEFT
or RIGHT
, why is the condition always false?
Upvotes: 5
Views: 6380
Reputation: 5358
in our case we forgot {}
as const A: React.FC<IProps> = (first, second, ...rest)
had to be const A: React.FC<IProps> = ({first, second, ...rest})
. This is awful :)
Upvotes: 0
Reputation: 141552
Why is action not of type Action as declared in the interface?
It is of a different type because the child class is re-defining the state
property and giving it a narrower type than it has in the parent class. If you are doing that by design, then you can access the broader interface of the parent class by casting inside render()
.
const isLateral = (this as Component<State>).state.action === Action.LEFT;
Alternatively, do what the React Component documentation says:
...if your component needs to use local state, assign the initial state to this.state directly in the constructor:
That is, do not re-redefine the parent class's state property; instead, use the already defined state
property that extend
gives you. Set its initial value in the constructor.
class MyComponent extends Component<State> {
constructor() {
super();
this.state = {
action: initialAction,
}
}
public render() {
const isLateral = this.state.action === Action.LEFT;
}
}
Upvotes: 3
Reputation: 2536
You have to type the public state to State like this:
public state: State = {
action: initialAction,
}
Otherwise Typescript will infer that state.action
is the same type as initialAction
.
You redefined the value and type of state
in your child class when you set as a new property.
My link illustrates that. I've added a new property on the parent class called count
with a type number
. If I define it in my child class as a new property, it infers it as any
, and thus can be set to a string within render()
.
However, you can see how I handled your original state
property. In order to use the parent's state type, you can call super()
in the constructor and then define this.state.action = initialAction;
.
That way, when you call render()
it will be looking for the Action
type on this.state.action
, and not the newly defined Action.UP | Action.DOWN
Upvotes: 1