Skip to content

Commit

Permalink
Fix setState to work in callback form and to allow partial intellisen…
Browse files Browse the repository at this point in the history
…se (#22127)

* Fix setState to work in callback form and to allow partial intellisense

* Fix lint and add tests

* Fix more lint

* Fixing more lint errors i didnt cause

* Add another test case

* Fix more lint
  • Loading branch information
ericanderson authored and johnnyreilly committed Dec 13, 2017
1 parent 77457a9 commit 62c2219
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 13 deletions.
11 changes: 6 additions & 5 deletions types/react/index.d.ts
Expand Up @@ -280,11 +280,12 @@ declare namespace React {
class Component<P, S> {
constructor(props: P, context?: any);

// Disabling unified-signatures to have separate overloads. It's easier to understand this way.
// tslint:disable-next-line:unified-signatures
setState<K extends keyof S>(f: (prevState: Readonly<S>, props: P) => Pick<S, K>, callback?: () => any): void;
// tslint:disable-next-line:unified-signatures
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> & Partial<S>)) | (Pick<S, K> & Partial<S>),
callback?: () => any
): void;

forceUpdate(callBack?: () => any): void;
render(): ReactNode;
Expand Down
17 changes: 17 additions & 0 deletions types/react/test/tsx.tsx
Expand Up @@ -77,3 +77,20 @@ const StatelessComponentWithoutProps: React.SFC = (props) => {
</React.Fragment>
</React.Fragment>
</div>;

class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
handleSomething = () => {
this.setState({ foo: '' }); // $ExpectError
this.setState({ foo: true });
this.setState({ foo: true, bar: true });
this.setState({});
this.setState({ foo: true, foo2: true }); // $ExpectError
this.setState(() => ({ foo: '' })); // $ExpectError
this.setState(() => ({ foo: true }));
this.setState(() => ({ foo: true, bar: true }));
this.setState(() => ({ foo: true, foo2: true })); // $ExpectError
this.setState(() => ({ foo: '', foo2: true })); // $ExpectError
this.setState(() => ({ })); // ok!
this.setState({ foo: true, bar: undefined}); // $ExpectError
}
}
16 changes: 9 additions & 7 deletions types/react/v15/index.d.ts
Expand Up @@ -282,11 +282,12 @@ declare namespace React {
class Component<P, S> {
constructor(props?: P, context?: any);

// Disabling unified-signatures to have separate overloads. It's easier to understand this way.
// tslint:disable:unified-signatures
setState<K extends keyof S>(f: (prevState: S, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
// tslint:enable:unified-signatures
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> & Partial<S>)) | (Pick<S, K> & Partial<S>),
callback?: () => any
): void;

forceUpdate(callBack?: () => any): void;
render(): JSX.Element | null | false;
Expand Down Expand Up @@ -3406,17 +3407,18 @@ declare namespace React {

declare global {
namespace JSX {
// tslint:disable:no-empty-interface
// tslint:disable-next-line:no-empty-interface
interface Element extends React.ReactElement<any> { }
interface ElementClass extends React.Component<any> {
render(): JSX.Element | null | false;
}
interface ElementAttributesProperty { props: {}; }
interface ElementChildrenAttribute { children: {}; }

// tslint:disable-next-line:no-empty-interface
interface IntrinsicAttributes extends React.Attributes { }
// tslint:disable-next-line:no-empty-interface
interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> { }
// tslint:enable:no-empty-interface

interface IntrinsicElements {
// HTML
Expand Down
2 changes: 1 addition & 1 deletion types/react/v15/test/index.ts
Expand Up @@ -294,7 +294,7 @@ myComponent.reset();
// Refs
// --------------------------------------------------------------------------

// tslint:disable:no-empty-interface
// tslint:disable-next-line:no-empty-interface
interface RCProps { }

class RefComponent extends React.Component<RCProps> {
Expand Down
17 changes: 17 additions & 0 deletions types/react/v15/test/tsx.tsx
Expand Up @@ -63,3 +63,20 @@ const StatelessComponentWithoutProps: React.SFC = (props) => {
return <div />;
};
<StatelessComponentWithoutProps />;

class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
handleSomething = () => {
this.setState({ foo: '' }); // $ExpectError
this.setState({ foo: true });
this.setState({ foo: true, bar: true });
this.setState({});
this.setState({ foo: true, foo2: true }); // $ExpectError
this.setState(() => ({ foo: '' })); // $ExpectError
this.setState(() => ({ foo: true }));
this.setState(() => ({ foo: true, bar: true }));
this.setState(() => ({ foo: true, foo2: true })); // $ExpectError
this.setState(() => ({ foo: '', foo2: true })); // $ExpectError
this.setState(() => ({ })); // ok!
this.setState({ foo: true, bar: undefined}); // $ExpectError
}
}

0 comments on commit 62c2219

Please sign in to comment.