Skip to content

Commit 9c2a12d

Browse files
secretsuperstar11090Armaan025
authored andcommitted
incorporate Ferdy's feedback
address typescript-cheatsheets/react#57
1 parent 0364a44 commit 9c2a12d

File tree

1 file changed

+85
-92
lines changed

1 file changed

+85
-92
lines changed

README.md

+85-92
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
:wave: This repo is maintained by [@swyx](https://twitter.com/swyx) and [@IslamAttrash](https://twitter.com/IslamAttrash), we're so happy you want to try out TypeScript with React! This is meant to be an intermediate guide for React developers familiar with the concepts of TypeScript but who are just getting started writing their first React + TypeScript apps. If you see anything wrong or missing, please [file an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new)! :+1:
32

43
Translations: [中文翻译](https://github.com/fi3ework/blog/tree/master/react-typescript-cheatsheet-cn) *maintained by [@fi3ework](https://github.com/fi3ework/blog/tree/master/react-typescript-cheatsheet-cn)*
@@ -16,8 +15,7 @@ Translations: [中文翻译](https://github.com/fi3ework/blog/tree/master/react-
1615
- [Section 2: Getting Started](#section-2-getting-started)
1716
* [Function Components](#function-components)
1817
* [Class Components](#class-components)
19-
* [Typing DefaultProps](#typing-defaultprops)
20-
* [Extracting Prop Types](#extracting-prop-types)
18+
* [Typing defaultProps](#typing-defaultprops)
2119
* [Types or Interfaces?](#types-or-interfaces)
2220
* [Basic Prop Types Examples](#basic-prop-types-examples)
2321
* [Useful React Type Examples](#useful-react-type-examples)
@@ -140,31 +138,42 @@ const Title: React.FunctionComponent<{ title: string }> = ({ children, title })
140138

141139
</details>
142140

143-
## Class Components
141+
<details>
142+
<summary><b>Common Pitfalls</b></summary>
144143

145-
Within TypeScript, `React.Component` is a generic type (aka `React.Component<PropType, StateType>`), so you actually want to provide it with prop and (optionally) state types:
144+
These patterns are not supported:
146145

147146
```tsx
148-
class App extends React.Component<{
149-
message: string, // it takes one prop called 'message' which is a string type
150-
}> {
151-
render() {
152-
return (
153-
<div>{this.props.message}</div>
154-
);
155-
}
156-
}
147+
const MyConditionalComponent = ({ shouldRender = false }) => shouldRender ? <div /> : false
148+
const el = <MyConditionalComponent /> // throws an error
149+
150+
const MyArrayComponent = () => Array(5).fill(<div />)
151+
const el2 = <MyArrayComponentt /> // throws an error
157152
```
158153

159-
If the component has state, here's how to add the types for the state:
154+
This is because due to limitations in the compiler, function components cannot return anything other than a JSX expression or `null`, otherwise it complains with a cryptic error message saying that the other type is not assignable to `Element`. Unfortunately just annotating the function type will not help so if you really need to return other exotic types that React supports, you'd need to perform a type assertion:
160155

161156
```tsx
162-
class App extends React.Component<{
163-
message: string, // this is the prop type
164-
}, {
165-
count: number, // this is the state type
166-
}> {
167-
state = {
157+
const MyArrayComponent = () => Array(5).fill(<div />) as any as JSX.Element
158+
```
159+
160+
[See commentary by @ferdaber here](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57).
161+
162+
</details>
163+
164+
## Class Components
165+
166+
Within TypeScript, `React.Component` is a generic type (aka `React.Component<PropType, StateType>`), so you want to provide it with (optional) prop and state type parameters:
167+
168+
```tsx
169+
type MyProps = { // using `interface` is also ok
170+
message: string
171+
}
172+
type MyState = {
173+
count: number // like this
174+
}
175+
class App extends React.Component<MyProps, MyState> {
176+
state: MyState = { // second annotation for better type inference
168177
count: 0
169178
}
170179
render() {
@@ -175,7 +184,19 @@ class App extends React.Component<{
175184
}
176185
```
177186

178-
If you need to define a clickhandler, just do it like normal, but just remember any arguments for your functions also need to be typed:
187+
Don't forget that you can export/import/extend these types/interfaces for reuse.
188+
189+
<details>
190+
<summary><b>Why annotate `state` twice?</b></summary>
191+
192+
It isn't strictly necessary to annotate the `state` class property, but it allows better type inference when accessing `this.state` and also initializing the state. This is because they work in two different ways, the 2nd generic type parameter will allow `this.setState()` to work correctly, because that method comes from the base class, but initializing `state` inside the component overrides the base implementation so you have to make sure that you tell the compiler that you're not actually doing anything different.
193+
194+
[See commentary by @ferdaber here](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57).
195+
196+
</details>
197+
198+
199+
**Class Methods**: Do it like normal, but just remember any arguments for your functions also need to be typed:
179200

180201
```tsx
181202
class App extends React.Component<{
@@ -199,7 +220,7 @@ class App extends React.Component<{
199220
}
200221
```
201222

202-
If you need to declare class properties for later use, just declare it with a type:
223+
**Class Properties**: If you need to declare class properties for later use, just declare it like `state`:
203224

204225
```tsx
205226
class App extends React.Component<{
@@ -219,85 +240,44 @@ class App extends React.Component<{
219240

220241
[Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new).
221242

222-
## Typing DefaultProps
243+
## Typing defaultProps
223244

224-
It is easy to type a defaultProps static member of a React component. There's more than one way to do it, but since we want to show the neatest code as possible
225-
we chose to propose this way of implementing them:
245+
There's more than one way to do it, but this is the best advice we've yet seen:
226246

227247
```ts
228-
interface IMyComponentProps {
229-
firstProp?: string;
230-
secondProp: IPerson[];
231-
}
248+
type Props = Required<typeof MyComponent.defaultProps> & { /* additional props here */ }
232249

233-
export class MyComponent extends React.Component<IMyComponentProps> {
234-
public static defaultProps: Partial<IMyComponentProps> = {
235-
firstProp: "default",
236-
};
250+
export class MyComponent extends React.Component<Props> {
251+
static defaultProps = {
252+
foo: 'foo'
253+
}
237254
}
238255
```
239256

240257
<details>
241258

242259
<summary>Explanation</summary>
243260

244-
This proposal is using `Partial type` feature in TypeScript, which means that the current interface will fulfill a partial
245-
version on the wrapped interface. In that way we can extend defaultProps without any changes in the types!
246-
247-
The other suggestions was related to create a new interface that will look like this:
261+
Our former recommendation used the `Partial type` feature in TypeScript, which means that the current interface will fulfill a partial version on the wrapped interface. In that way we can extend defaultProps without any changes in the types!
248262

249263
```ts
250264
interface IMyComponentProps {
251-
firstProp: string;
265+
firstProp?: string;
252266
secondProp: IPerson[];
253267
}
254268

255-
interface IMyComponentDefaultProps {
256-
firstProp: string;
257-
}
258-
259-
export class MyComponent extends React.Component<IMyComponentProps, {}> {
260-
static defaultProps: IMyComponentDefaultProps = {
269+
export class MyComponent extends React.Component<IMyComponentProps> {
270+
public static defaultProps: Partial<IMyComponentProps> = {
261271
firstProp: "default",
262272
};
263273
}
264274
```
265275

266-
The problem with this approach that if we need to add another prop in the future to the defaultProps map then we should update the
267-
`IMyComponentDefaultProps`!
268-
</details>
269-
270-
[Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new).
276+
The problem with this approach is it causes complex issues with the type inference working with `JSX.LibraryManagedAttributes`. Basically it causes the compiler to think that when creating a JSX expression with that component, that all of its props are optional.
271277

272-
## Extracting Prop Types
278+
[See commentary by @ferdaber here](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57).
273279

274-
Instead of defining prop types *inline*, you can declare them separately (useful for reusability or code organization):
275-
276-
```tsx
277-
type AppProps = { message: string }
278-
const App: React.FunctionComponent<AppProps> = ({ message }) => <div>{message}</div>;
279-
```
280-
281-
You can also do this for stateful component types (really, any types):
282-
283-
```tsx
284-
type AppProps = { // like this
285-
message: string,
286-
}
287-
type AppState = { // and this
288-
count: number,
289-
}
290-
class App extends React.Component<AppProps, AppState> {
291-
state = {
292-
count: 0
293-
}
294-
render() {
295-
return (
296-
<div>{this.props.message} {this.state.count}</div>
297-
);
298-
}
299-
}
300-
```
280+
</details>
301281

302282
[Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new).
303283

@@ -320,21 +300,29 @@ type AppProps = {
320300
message: string,
321301
count: number,
322302
disabled: boolean,
323-
names: string[], // array of a type!
324-
obj: object, // any object as long as you dont use it in your typescript code
303+
// array of a type!
304+
names: string[],
305+
// any object as long as you dont use its properties (not common)
306+
obj: object,
325307
obj2: {}, // same
326-
object: {
308+
// an object with defined properties (preferred)
309+
obj3: {
327310
id: string,
328311
title: string
329-
}, // an object with defined properties
330-
objects: {
312+
},
313+
// array of objects! (common)
314+
objArr: {
331315
id: string,
332316
title: string
333-
}[], // array of objects!
334-
onSomething: Function, // not recommended
335-
onClick: () => void, // function that doesn't return anything
336-
onChange: (id: number) => void, // function with named prop
337-
optional?: OptionalType, // an optional prop
317+
}[],
318+
// any function as long as you don't invoke it (not recommended)
319+
onSomething: Function,
320+
// function that doesn't take or return anything (VERY COMMON)
321+
onClick: () => void,
322+
// function with named prop (VERY COMMON)
323+
onChange: (id: number) => void,
324+
// an optional prop (VERY COMMON!)
325+
optional?: OptionalType,
338326
}
339327
```
340328
@@ -356,8 +344,13 @@ export declare interface AppProps {
356344

357345
## Forms and Events
358346

359-
This can be a bit tricky. The tooling really comes in handy here, as the @type definitions come with a wealth of typing. Type what you are looking for and usually the autocomplete will help you out. Here is what it looks like for an `onChange` for a form event:
347+
If performance is not an issue, inlining handlers is easiest as you can just use type inference:
360348

349+
```tsx
350+
const el = <button onClick={event => {/* ... */}} />
351+
```
352+
353+
But if you need to define your event handler separately, IDE tooling really comes in handy here, as the @type definitions come with a wealth of typing. Type what you are looking for and usually the autocomplete will help you out. Here is what it looks like for an `onChange` for a form event:
361354

362355
```tsx
363356
class App extends React.Component<{}, { // no props
@@ -366,6 +359,9 @@ class App extends React.Component<{}, { // no props
366359
state = {
367360
text: ''
368361
}
362+
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
363+
this.setState({text: e.currentTarget.value})
364+
}
369365
render() {
370366
return (
371367
<div>
@@ -377,9 +373,6 @@ class App extends React.Component<{}, { // no props
377373
</div>
378374
);
379375
}
380-
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
381-
this.setState({text: e.currentTarget.value})
382-
}
383376
}
384377
```
385378

0 commit comments

Comments
 (0)