Skip to content

Mapped types with strictNullChecks and React setState() - Could this be a reliable solution? #12801

Closed
@vknez

Description

@vknez

TypeScript Version: 2.1.4

The idea

I came up with this definition of React's setState, that should offer (under strictNullChecks) the following:

  1. accepting a state patch object with any combination of properties of the State type, even without non-null properties specified
  2. 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 operation keyof { [P in K]: X } is equivalent to just K. For example, keyof Partial<T> is equivalent to keyof T.

... I am afraid that it might only work because of a bug, for the following reasons:

  1. Shouldn't nonNullable1?: number; be the same as nonNullable1: number | undefined;? If so, calling the function like this setState({ nonNullable1: undefined }) should report no error.
  2. @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

  1. Is this behavior here to stay, or it results from a bug in TS, and
  2. If this particular behavior is by design, can this solution be considered a safe setState() definition to use in React with strictNullChecks?

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'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions