Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript partial interface object

In React, the Component definition looks something like this:

class Component<S> {
    state:S;
    setState(state:S):void;
}

And you define a component like this:

interface MyState {
    name: string;
    age: number;
}
class MyComponent extends Component<MyState> { }

Now the issue I have is that in the React API setState is supposed to be called with a partial state object, representing properties to be updated, like this:

setState({name: "Aaron"});

Note that age is not declared. The problem I have is that TypeScript doesn't allow this, it gives an assignment error like Property 'age' is missing. By my understanding, the react.d.ts definition is wrong in this sense. But is there a solution?

I tried this:

setState({name: "Aaron"} as MyState);

But this gives the same error, even though it works in the Playground without giving an error. Why does it work in the Playground? Any ideas?

like image 780
Aaron Beall Avatar asked Apr 14 '16 20:04

Aaron Beall


2 Answers

"Partial types" are still missing in TypeScript currently, see TypeScript issue #4889 as well as this related question. I'm afraid it's not yet possible to make this type-check correctly.

You might get away with marking all fields of your MyState interface as optional (by adding ? modifiers), but that in turn weakens the type-checking for things like Component.state (where you want all fields to be set).

EDIT (December 2016): TypeScript 2.1 introduces mapped types, which supports describing partial types using Partial<T>! Now you can use the following type definition for Component:

class Component<S> {
    state: S;
    setState(state: Partial<S>) : void;
}
like image 110
Mattias Buelens Avatar answered Nov 02 '22 19:11

Mattias Buelens


The react.d.ts definition is indeed wrong, and cannot be fixed until Partial Types are supported.

The problem is that setState takes a partial state, which is a different type from normal state. You can solve this by manually specifying both types:

interface MyState {
    name: string;
    age: number;
}

interface PartialMyState {
    name?: string;
    age?: number;
}

And manually declaring MyComponent without extending the provided React Component class:

class MyComponent {
    state: MyState;
    setState(state:PartialMyState): void;
    //...
}

Which means you'll have to duplicate these function definitions for every subclass of Component in your code. You may be able to avoid this by defining a correct Component class generalized by an additional type of partial state:

class CorrectComponent<S,P> { // generalized over both state and partial state
    state:S;
    setState(state:P):void;
    //...
}

class MyComponent extends CorrectComponent<MyState,PartialMyState> { }

You'll still have to write a partial version for every state type you have.


Alternatively, you can make setState non-typesafe by changing its argument's type to Object.

like image 42
Spike Avatar answered Nov 02 '22 19:11

Spike