Skip to content

Commit 542f3c0

Browse files
committed
feat(react): improve props and state typesafety on Component
- fix(react): remove failing expectation on TS 2.6 - chore(react): add myself to contributors
1 parent 61a609c commit 542f3c0

3 files changed

Lines changed: 92 additions & 3 deletions

File tree

types/react/index.d.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// Ferdy Budhidharma <https://github.com/ferdaber>
1818
// Johann Rakotoharisoa <https://github.com/jrakotoharisoa>
1919
// Olivier Pascal <https://github.com/pascaloliv>
20+
// Martin Hochel <https://github.com/hotell>
2021
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
2122
// TypeScript Version: 2.6
2223

@@ -281,13 +282,18 @@ declare namespace React {
281282
// tslint:disable-next-line:no-empty-interface
282283
interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
283284
class Component<P, S> {
285+
constructor(props: Readonly<P>);
286+
/**
287+
* @deprecated
288+
* https://reactjs.org/docs/legacy-context.html
289+
*/
284290
constructor(props: P, context?: any);
285291

286292
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
287293
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
288294
// Also, the ` | S` allows intellisense to not be dumbisense
289295
setState<K extends keyof S>(
290-
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
296+
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
291297
callback?: () => void
292298
): void;
293299

@@ -299,9 +305,17 @@ declare namespace React {
299305
// always pass children as variadic arguments to `createElement`.
300306
// In the future, if we can define its call signature conditionally
301307
// on the existence of `children` in `P`, then we should remove this.
302-
props: Readonly<{ children?: ReactNode }> & Readonly<P>;
303-
state: Readonly<S>;
308+
readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
309+
readonly state: null | Readonly<S>;
310+
/**
311+
* @deprecated
312+
* https://reactjs.org/docs/legacy-context.html
313+
*/
304314
context: any;
315+
/**
316+
* @deprecated
317+
* https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs
318+
*/
305319
refs: {
306320
[key: string]: ReactInstance
307321
};

types/react/test/index.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,72 @@ declare const container: Element;
5454
//
5555
// Top-Level API
5656
// --------------------------------------------------------------------------
57+
{
58+
interface State {
59+
inputValue: string;
60+
seconds: number;
61+
}
62+
/**
63+
* This is a "pre EcmaScript class property era" style of setting state within constructor.
64+
* This occurs also if you need to provide som logic before mounting your component.
65+
* To mitigate this error you need to provide state property definition upfront.
66+
*/
67+
class SettingStateFromCtorComponent extends React.Component<Props, State, Snapshot> {
68+
// uncomenting this fixes the error :)
69+
// state: State;
70+
constructor(props: Props) {
71+
super(props);
72+
// $ExpectError
73+
this.state = {
74+
inputValue: 'hello'
75+
};
76+
}
77+
render() { return null; }
78+
}
79+
80+
class BadlyInitializedState extends React.Component<Props, State, Snapshot> {
81+
// ExpectError -> this throws error on TS 2.6
82+
// state = {
83+
// secondz: 0,
84+
// inputValuez: 'hello'
85+
// };
86+
}
87+
class BetterPropsAndStateChecksComponent extends React.Component<Props, State, Snapshot> {
88+
render() { return null; }
89+
componentDidMount() {
90+
// $ExpectError
91+
console.log(this.state.inputValue);
92+
}
93+
mutateState() {
94+
// $ExpectError
95+
this.state = {
96+
inputValue: 'hello'
97+
};
98+
99+
// Even if state is not set, this is allowed by React
100+
this.setState({inputValue: 'hello'});
101+
this.setState((prevState, props) => {
102+
// $ExpectError
103+
props = {foo: 'nope'};
104+
// $ExpectError
105+
props.foo = 'nope';
106+
107+
return { inputValue: prevState.inputValue + ' foo' };
108+
});
109+
}
110+
mutateProps() {
111+
// $ExpectError
112+
this.props = {};
113+
// $ExpectError
114+
this.props = {
115+
key: 42,
116+
ref: "myComponent42",
117+
hello: "world",
118+
foo: 42
119+
};
120+
}
121+
}
122+
}
57123

58124
class ModernComponent extends React.Component<Props, State, Snapshot>
59125
implements MyComponent, React.ChildContextProvider<ChildContext> {
@@ -682,6 +748,7 @@ React.createFactory(TransitionGroup)({ component: "div" });
682748
// The SyntheticEvent.target.value should be accessible for onChange
683749
// --------------------------------------------------------------------------
684750
class SyntheticEventTargetValue extends React.Component<{}, { value: string }> {
751+
state: { value: string };
685752
constructor(props: {}) {
686753
super(props);
687754
this.state = { value: 'a' };

types/react/test/tsx.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ class ComponentWithNewLifecycles extends React.Component<NewProps, NewState, { b
141141
return { bar: `${nextProps.foo}bar` };
142142
}
143143

144+
state = {
145+
bar: 'foo'
146+
};
147+
144148
getSnapshotBeforeUpdate(prevProps: Readonly<NewProps>) {
145149
return { baz: `${prevProps.foo}baz` };
146150
}
@@ -160,6 +164,10 @@ class PureComponentWithNewLifecycles extends React.PureComponent<NewProps, NewSt
160164
return { bar: `${nextProps.foo}bar` };
161165
}
162166

167+
state = {
168+
bar: 'foo'
169+
};
170+
163171
getSnapshotBeforeUpdate(prevProps: Readonly<NewProps>) {
164172
return { baz: `${prevProps.foo}baz` };
165173
}

0 commit comments

Comments
 (0)