Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React :: partial State on setState #11740

Closed

Conversation

tiagoefmoraes
Copy link
Contributor

https://github.com/DefinitelyTyped/DefinitelyTyped/tree/types-2.0/react#the-type-of-setstate-is-incorrect

Changing React.Component setState from

setState(state: S, callback?: () => any): void;

to

setState<S>(this: Component<P, S>, state: S, callback?: () => any): void;

removes the need to define State properties as optional (?).

Important change is in react/index.d.ts other changes are just fixing tests for other components.

This may not be the final solution after typescript adds proper support for partial types, but I wanted to discuss this solution with the community.

@tiagoefmoraes tiagoefmoraes changed the title partial State on setState React :: partial State on setState Oct 5, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Oct 5, 2016

@bbenezech, @joshaber, @neelance and @DanielRosenwasser can you please review this change?

@joshaber
Copy link
Contributor

joshaber commented Oct 6, 2016

Wow, it'd be great if we didn't have to pass all of State to setState!

I'm not sure I understand how/why this works, though. Could you explain it?

Copy link
Contributor

@bbenezech bbenezech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed results with (ironically) optional State properties:

// with TS2.0.3, StrictNullChecks to true,
// removing first noisy setState overloading (irrelevant here) `setState(f: (prevState: S, props: P) => S, callback?: () => any): void;`

interface S {
  a: string,
  b: string
}

class Test extends React.PureComponent<{}, S> {

  componentDidMount() {
    // [ts] The type argument for type parameter 'S' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
    // Type argument candidate 'S' is not a valid type argument because it is not a supertype of candidate '{ a: string; }'.
    // Property 'b' is missing in type '{ a: string; }'.
    // (method) React.Component<{}, S>.setState<S>(this: React.Component<{}, S>, state: S, callback?: (() => any) | undefined): void
    this.setState({
      a: 'test'
    })

    // [ts] Argument of type '{ a: string; }' is not assignable to parameter of type 'S'.
    // Property 'b' is missing in type '{ a: string; }'.
    this.setState<S>({
      a: 'test'
    })

    // passes
    this.setState({
      a: undefined,
      b: 'test'
    })

    // passes
    this.setState({
      a: 'test',
      b: 'test'
    })
  }
}

@bbenezech
Copy link
Contributor

bbenezech commented Oct 7, 2016

sorry, should read

interface S {
  a: string|undefined,
  b: string
}

(dumb last second change, cannot edit review)

@tiagoefmoraes
Copy link
Contributor Author

@joshaber What I understand is that typescript infers that the State of the component is a supertype of the state parameter, this is what the error message says:

interface ComponentState {
  a: string;
}
export class Test extends React.PureComponent<{}, ComponentState> {
  componentDidMount() {
    // [ts] The type argument for type parameter 'S' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
    //   Type argument candidate 'ComponentState' is not a valid type argument because it is not a supertype of candidate '{ wrong: string; }'.
    //     Object literal may only specify known properties, and 'wrong' does not exist in type 'ComponentState'.
    // (property) wrong: string
    this.setState({
      wrong: 'test'
    });
  }
}

@bbenezech I had to remove all optional properties from the tests. In my code I'm not using them, instead I supply default blank/empty values.
Don't think anything else can be done right now about this, unfortunately.

@bbenezech
Copy link
Contributor

I encourage you not to merge this.

Apart from the optional pb, which is a big one,
this.setState works but not this.setState<S> when using subsets.

It means the inference on S is not working as intended.
We are tricking the compiler to think that S is the hash passed instead of the one from the class definition. Weird.

@joshaber
Copy link
Contributor

joshaber commented Oct 7, 2016

I agree, especially as TypeScript is working on real support for subsets.

@DanielRosenwasser
Copy link
Member

@RyanCavanaugh can you glance at this?

@DanielRosenwasser
Copy link
Member

It still looks like you have to pass in the full state in most cases as @bbenezech mentioned above. Why not try something like

setState<PartialState, ActualState extends PartialState>(this: Component<P, ActualState>, state: PartialState, callback?: () => any)

@bbenezech
Copy link
Contributor

bbenezech commented Oct 8, 2016

@DanielRosenwasser Interesting approach, but no dice.

interface S {
  a?: string,
  b: string
}

// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
this.setState({
  a: 'test'
})

// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
this.setState({
  a: 'test',
  b: 'test'
})

// Property b is missing from type S
this.setState({
  a: 'test'
} as S)

// this works
this.setState({
  a: 'test',
  b: 'test'
} as S)

Sorry folks :(

@DanielRosenwasser
Copy link
Member

Luckily we're looking into non-hacky ways of declaring this.

@tiagoefmoraes tiagoefmoraes deleted the setState branch October 18, 2017 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants