Closed
Description
TypeScript Version: 2.1.4
The idea
I came up with this definition of React's setState
, that should offer (under strictNullChecks
) the following:
- accepting a state patch object with any combination of properties of the
State
type, even without non-null properties specified - when a non-null property is present in the state patch object, TS must not allow setting it to
null | undefined
function setState(_patch: {[P in keyof Partial<State>]: State[P]}): void {
}
interface State {
nonNullable1: number;
nonNullable2: string;
nullable?: string;
}
Potential issues
It seems that this satisfies my requirements, as you can see below in the code extract, but when I look at the mapped type definition:
{
nonNullable1?: number;
nonNullable2?: string;
nullable?: string | undefined;
}
... and one particular statement in #12351:
The PR furthermore adds the following rules:
The operationkeyof { [P in K]: X }
is equivalent to justK
. For example,keyof Partial<T>
is equivalent tokeyof T
.
... I am afraid that it might only work because of a bug, for the following reasons:
- Shouldn't
nonNullable1?: number;
be the same asnonNullable1: number | undefined;
? If so, calling the function like thissetState({ nonNullable1: undefined })
should report no error. - @ahejlsberg's comment does not hold here, because if I change the definition to:
_patch: {[P in keyof State]: State[P]}
, TS complains in all the function calls below in the code extract and the mapped type definition becomes:
{
nonNullable1: number;
nonNullable2: string;
nullable?: string | undefined;
}
Questions
- Is this behavior here to stay, or it results from a bug in TS, and
- If this particular behavior is by design, can this solution be considered a safe
setState()
definition to use in React withstrictNullChecks
?
Full code example
interface State {
nonNullable1: number;
nonNullable2: string;
nullable?: string;
}
function setState(_patch: {[P in keyof Partial<State>]: State[P]}): void {
}
setState({
});
// passes
setState({
nullable: "test",
});
// passes
setState({
nullable: undefined,
});
// passes
setState({
nonNullable1: 1,
});
// passes
setState({
nonNullable1: undefined,
});
// Argument of type '{ nonNullable1: undefined; }' is not assignable to parameter of type '{ nonNullable1?: number; nonNullable2?: string; nullable?: string | undefined; }'.
// Types of property 'nonNullable1' are incompatible. Type 'undefined' is not assignable to type 'number'.