Why does null React component state initialization get `never` type?
You can definitely initialize the state outside of the constructor, but you need to be sure to type it so that TypeScript can reconcile the type of the initial value and the type of the generic. Try:
class PersonSelector extends React.Component<Props, State> {
state: State = {
selected: null
};
// ...
}
According to most questions on StackOverflow about the two ways to initialize state (in babeled JS), these two methods should be equivalent. However, in Typescript, they are not.
They are different in TypeScript because assigning state
in the class body (not in the constructor) declares state
in PersonSelector
, overriding the declaration in base class React.Component
. In TypeScript, overriding declaration is allowed to have different, more strict type, one-way compatible with the type of the same property in the base class.
When initialized without type annotation, this type is determined from the type of the value:
class PersonSelector extends React.Component<Props, State> {
// DOES NOT WORK:
state = {
selected: null
};
You can see that type of the state
is {selected: null}
, as expected. It becomes never
in this code
const { selected } = this.state;
let selectedLabel = <div>None selected</div>;
if (selected) {
because inside if
statement, the type of selected
is narrowed, using the information that selected
is true
. Null can never be true, so the type becomes never
.
As suggested in other answer, you can annotate State
explicitly when initializing in the class body
class PersonSelector extends React.Component<Props, State> {
state: State = {
selected: null
};
Update to clarify how the initialization in the class body is different from assigning in the constructor
When you set state
in the constructor
constructor(props: Props) {
super(props);
this.state = {
selected: null
};
}
you are assigning the value to state
property that already exists as it was declared in the base class. The base class is React.Component<Props, State>
, and state
property there is declared to have State
type, taken from the second generic argument in <Props, State>
.
Assignments do not change the type of the property - it remains State
, regardless of the value assigned.
When you set state
in the class body it's not mere assignment - it's a declaration of class property, and each declaration gives a type to the declared entity - either explicitly via type annotation, or implicitly, inferred from the initial value. This typing happens even if the property already exists in the base class. I can't find anything in the documentation that confirms this, but there is github issue that describes exactly this behavior, and confirms that sometimes it goes against the developer's intent (no solution implemented in the language so far).