26

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?

1
  • I somehow managed to get partial types working with Object.assign (fully working with type-checking and without having to create interface declarations with optional members), but I'm still unsure how. The very same code works in one project but not in another. Once I figure it out I'll post the update. Commented Sep 6, 2016 at 13:01

3 Answers 3

30

"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;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Only to give an example of the given workaround, in your case, your interface would be: interface MyState { name?: string; age?: number; }
Thanks. Indeed I was avoiding marking all fields as optional because it's not really true... any idea why my Playground example works? It seems like it should not?
@Aaron That example works because the as MyState acts as an override for the type checker. Still, I don't see how the same code would fail in your local build...
@MattiasBuelens It seems to have something to do with optional properties. See here. What is the difference here?
@MattiasBuelens I've accepted this answer, however I'm still confused why interface S {a: string; b: string}; works with let s:S = {a:"hi"} as S;, but if I make a optional, like interface S { a?: string; b: string}, then it gives an error on assignment/assertion that b is missing?
6

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.

Comments

4

As other people have stated, since TypeScript 2.1, the Partial keyword has been implemented.

However for future readers, it is worth noting the difference in syntax between Interfaces and Types when extending them.

For Interfaces:

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

interface MyStatePartial extends Partial<MyState> {}
// Results in:
// {
//     name?: string;
//     age?: number;
// }

Note the reason for the empty curly braces ({}) at the end is because I'm extending the interface, but not adding any new keys (See Docs)

For Types:

type MyState = {
    name: string;
    age: number;
}

type MyStatePartial = Partial<MyState>;

Note: If you want similar syntax to the interface version you could do:

type MyStatePartial = Partial<MyState> & {};

Which, while redundant does allow the reader to see the syntactical similarities between Types and Interfaces

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.