diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f37a20c4..c3021e4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,6 @@ I thought I should lay out some core principles that we will follow so that this 1. **We are a CHEATSHEET above all**: all examples to be as simple as possible, easily searched, and presented for copy-and-paste. 2. **Collapsible explanations**: No more than 1-2 sentences of explanation, any more than that we put inside `details` tags. -3. **React + TypeScript ONLY**: React's ecosystem is huge, we can't possibly cover it all. This includes Redux. Would encourage people to maintain separate lists for stuff like React + Apollo Graphql, for example. +3. **React + TypeScript ONLY**: React's ecosystem is huge, we can't possibly cover it all. This includes Redux. Would encourage people to maintain separate lists for stuff like React + Apollo Graphql, for example. Also we make no attempt to convince people to use TypeScript, we only exist to help people who have already decided to try it out. That's all I've got! Again, really happy you are thinking about helping out, who knows, the person who you might be helping is yourself in future! diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 00000000..242040ed --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,128 @@ +# Migrating (to TypeScript) Cheatsheet + +This Cheatsheet collates advice and utilities from real case studies of teams moving significant codebases from plain JS or Flow over to TypeScript. It makes no attempt to _convince_ people to do so, but we do collect what few statistics companies offer up after their conversion experience. + +> ⚠️ This Cheatsheet is extremely new and could use all the help we can get. Solid advice, results, and up to date content all welcome. + +## General Conversion approaches + +- Level 0: Don't use TypeScript, use JSDoc + - See our [JSDoc section](#JSDoc) +- Level 1: Unstrict TypeScript + - `"noImplicitAny": false` + - "[Just rename all .js files to .ts](https://twitter.com/jamonholmgren/status/1089241726303199232)" + - consider using `allowJS`? (Source: [clayallsop][clayallsop], [pleo][pleo]) +- Level 2: Strict TypeScript + - use Microsoft's [`dts-gen`](https://github.com/Microsoft/dts-gen) to generate `.d.ts` files for your untyped files. [This SO answer](https://stackoverflow.com/questions/12687779/how-do-you-produce-a-d-ts-typings-definition-file-from-an-existing-javascript) has more on the topic. + - use `declare` keyword for ambient declarations + + +Misc tips/approaches successful companies have taken + +- `@ts-ignore` on compiler errors for libraries with no typedefs +- pick ESLint over TSLint ([source](https://eslint.org/blog/2019/01/future-typescript-eslint)) +- New code must always be written in TypeScript. No exceptions. For existing code: If your task requires you to change JavaScript code, you need to rewrite it. (Source: [Hootsuite][hootsuite]) + + +
+ + +Webpack tips + + + +- webpack loader: `awesome-typescript-loader` vs `ts-loader`? (there is some disagreement in community about this) +- Webpack config: + +``` +module.exports = { + +resolve: { +- extensions: ['.js', '.jsx'] ++ extensions: ['.ts', '.tsx', '.js', '.jsx'] +}, + +// Source maps support ('inline-source-map' also works) +devtool: 'source-map', + +// Add the loader for .ts files. +module: { + loaders: [{ +- test: /\.jsx?$/, +- loader: 'babel-loader', +- exclude: [/node_modules/], ++ test: /\.(t|j)sx?$/, ++ loader: ['awesome-typescript-loader?module=es6'], ++ exclude: [/node_modules/] ++ }, { ++ test: /\.js$/, ++ loader: 'source-map-loader', ++ enforce: 'pre' + }] +} +}; +``` + +
+ +## JSDoc + +- https://github.com/Microsoft/TypeScript/wiki/JsDoc-support-in-JavaScript +- webpack's codebase uses JSDoc with linting by TS https://twitter.com/TheLarkInn/status/984479953927327744 (some crazy hack: https://twitter.com/thelarkinn/status/996475530944823296) + +Problems to be aware of: + +- `object` is converted to `any` for some reason. +- If you have an error in the jsdoc, you get no warning/error. TS just silently doesn't type annotate the function. +- [casting can be verbose](https://twitter.com/bahmutov/status/1089229349637754880) + +(*thanks [Gil Tayar](https://twitter.com/giltayar/status/1089228919260221441) and [Gleb Bahmutov](https://twitter.com/bahmutov/status/1089229196247908353) for sharing above commentary*) + +## From JS + +- [TypeScript's official Guide for migrating from JS](https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html) +- [Migrating a `create-react-app`/`react-scripts` app to TypeScript](https://facebook.github.io/create-react-app/docs/adding-typescript) - don't use `react-scripts-ts` +- [Migrating an EJECTED CRA app to TS](https://spin.atomicobject.com/2018/07/04/migrating-cra-typescript/) +- [Lyft's JS to TS migration tool](https://github.com/lyft/react-javascript-to-typescript-transform) (includes PropTypes migration) +- [Hootsuite][hootsuite] +- [Storybook's migration (PR)](https://github.com/storybooks/storybook/issues/5030) +- [How we migrated a 200K+ LOC project to TypeScript and survived to tell the story][coherentlabs] - Coherent Labs - using `grunt-ts`, jQuery and Kendo UI + +Old content that is possibly out of date + +- [Incrementally Migrating JS to TS][clayallsop] (old) +- [Microsoft's TypeScript React Conversion Guide][mstsreactconversionguide] (old) + +## From Flow + +- Try flow2ts: `npx flow2ts` - doesn't work 100% but saves some time ([see this and other tips from @braposo](https://github.com/sw-yx/react-typescript-cheatsheet/pull/79#issuecomment-458227322) at TravelRepublic) +- [Incremental Migration to TypeScript on a Flowtype codebase][entria] at Entria +- [Jest's migration (PR)](https://github.com/facebook/jest/pull/7554#issuecomment-454358729) +- [Expo's migration (issue)](https://github.com/expo/expo/issues/2164) +- [Atlassian's migration (PR)](https://github.com/atlassian/react-beautiful-dnd/issues/982) +- [Yarn's migration (issue)](https://github.com/yarnpkg/yarn/issues/6953) +- [MemSQL's Studio's migration](https://davidgom.es/porting-30k-lines-of-code-from-flow-to-typescript/) - blogpost with many useful tips + +## Results + +- Number of production deploys doubled for [Hootsuite][hootsuite] +- Found accidental globals for [Tiny][tiny] +- Found incorrect function calls for [Tiny][tiny] +- Found rarely used, buggy code that was untested for [Tiny][tiny] + +## Misc writeups by notable companies + +- [Lyft](https://eng.lyft.com/typescript-at-lyft-64f0702346ea) +- [Google](http://neugierig.org/software/blog/2018/09/typescript-at-google.html) +- [Tiny][tiny] - [Talk from ForwardJS here](https://www.slideshare.net/tiny/porting-100k-lines-of-code-to-typescript) +- [Slack](https://slack.engineering/typescript-at-slack-a81307fa288d) + +## Links + +[hootsuite]: https://medium.com/hootsuite-engineering/thoughts-on-migrating-to-typescript-5e1a04288202 'Thoughts on migrating to TypeScript' +[clayallsop]: https://medium.com/@clayallsopp/incrementally-migrating-javascript-to-typescript-565020e49c88 'Incrementally Migrating JavaScript to TypeScript' +[pleo]: https://medium.com/pleo/migrating-a-babel-project-to-typescript-af6cd0b451f4 'Migrating a Babel project to TypeScript' +[mstsreactconversionguide]: https://github.com/Microsoft/TypeScript-React-Conversion-Guide 'TypeScript React Conversion Guide' +[entria]: https://medium.com/entria/incremental-migration-to-typescript-on-a-flowtype-codebase-515f6490d92d 'Incremental Migration to TypeScript on a Flowtype codebase' +[coherentlabs]: https://hashnode.com/post/how-we-migrated-a-200k-loc-project-to-typescript-and-survived-to-tell-the-story-ciyzhikcc0001y253w00n11yb 'How we migrated a 200K+ LOC project to TypeScript and survived to tell the story' +[tiny]: https://go.tiny.cloud/blog/benefits-of-gradual-strong-typing-in-javascript/ 'Benefits of gradual strong typing in JavaScript' diff --git a/README.md b/README.md index ab825af1..1e3d6b7d 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,27 @@ :wave: This repo is maintained by [@swyx](https://twitter.com/swyx), [@IslamAttrash](https://twitter.com/IslamAttrash) and [@ferdaber](https://twitter.com/ferdaber), we're so happy you want to try out TypeScript with React! This is meant to be a 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: -Translations: +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)* +- [中文翻译](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)_ - Your language here? --- -## Two Levels of React + TypeScript Cheatsheets +## React + TypeScript Cheatsheets - **The Basic Cheatsheet** ([`/README.md`](/README.md)) is focused on helping React devs just start using TS in React **apps** - focus on opinionated best practices, copy+pastable examples - explains some basic TS types usage and setup along the way - answers the most Frequently Asked Questions - does not cover generic type logic in detail. Instead we prefer to teach simple troubleshooting techniques for newbies. - - The goal is to get effective with TS without learning *too much* TS. + - The goal is to get effective with TS without learning _too much_ TS. - **The Advanced Cheatsheet** ([`/ADVANCED.md`](/ADVANCED.md)) helps show and explain advanced usage of generic types for people writing reusable type utilities/functions/render prop/higher order components and TS+React **libraries**. - - It also has miscellaneous tips and tricks for pro users. + - It also has miscellaneous tips and tricks for pro users. - Advice for contributing to DefinitelyTyped - - The goal is to take *full advantage* of TypeScript. + - The goal is to take _full advantage_ of TypeScript. +- **The Migrating Cheatsheet** ([`/MIGRATING.md`](/MIGRATING.md)) helps collate advice for incrementally migrating large codebases from JS or Flow, **from people who have done it**. + - We do not try to convince people to switch, only to help people who have already decided + - ⚠️This is the newest cheatsheet, all assistance is welcome --- @@ -29,32 +32,32 @@ Translations: Expand Table of Contents - [Section 1: Setup](#section-1-setup) - * [Prerequisites](#prerequisites) - * [React + TypeScript Starter Kits](#react--typescript-starter-kits) - * [Import React](#import-react) + - [Prerequisites](#prerequisites) + - [React + TypeScript Starter Kits](#react--typescript-starter-kits) + - [Import React](#import-react) - [Section 2: Getting Started](#section-2-getting-started) - * [Function Components](#function-components) - * [Hooks](#hooks) - * [Class Components](#class-components) - * [Typing defaultProps](#typing-defaultprops) - * [Types or Interfaces?](#types-or-interfaces) - * [Basic Prop Types Examples](#basic-prop-types-examples) - * [Useful React Prop Type Examples](#useful-react-prop-type-examples) - * [Forms and Events](#forms-and-events) - * [Context](#context) - * [forwardRef/createRef](#forwardrefcreateref) - * [Portals](#portals) - * [Error Boundaries](#error-boundaries) - * [Concurrent React/React Suspense](#concurrent-reactreact-suspense) + - [Function Components](#function-components) + - [Hooks](#hooks) + - [Class Components](#class-components) + - [Typing defaultProps](#typing-defaultprops) + - [Types or Interfaces?](#types-or-interfaces) + - [Basic Prop Types Examples](#basic-prop-types-examples) + - [Useful React Prop Type Examples](#useful-react-prop-type-examples) + - [Forms and Events](#forms-and-events) + - [Context](#context) + - [forwardRef/createRef](#forwardrefcreateref) + - [Portals](#portals) + - [Error Boundaries](#error-boundaries) + - [Concurrent React/React Suspense](#concurrent-reactreact-suspense) - [Basic Troubleshooting Handbook: Types](#basic-troubleshooting-handbook-types) - * [Union Types and Type Guarding](#union-types-and-type-guarding) - * [Optional Types](#optional-types) - * [Enum Types](#enum-types) - * [Type Assertion](#type-assertion) - * [Intersection Types](#intersection-types) - * [Using Inferred Types](#using-inferred-types) - * [Using Partial Types](#using-partial-types) - * [The Types I need Weren't Exported!](#the-types-i-need-werent-exported) + - [Union Types and Type Guarding](#union-types-and-type-guarding) + - [Optional Types](#optional-types) + - [Enum Types](#enum-types) + - [Type Assertion](#type-assertion) + - [Intersection Types](#intersection-types) + - [Using Inferred Types](#using-inferred-types) + - [Using Partial Types](#using-partial-types) + - [The Types I need Weren't Exported!](#the-types-i-need-werent-exported) - [Troubleshooting Handbook: TSLint](#troubleshooting-handbook-tslint) - [Troubleshooting Handbook: tsconfig.json](#troubleshooting-handbook-tsconfigjson) - [Recommended React + TypeScript codebases to learn from](#recommended-react--typescript-codebases-to-learn-from) @@ -62,7 +65,7 @@ Translations: - [Editor Tooling and Integration](#editor-tooling-and-integration) - [Other React + TypeScript resources](#other-react--typescript-resources) - [Time to Really Learn TypeScript](#time-to-really-learn-typescript) - + # Section 1: Setup @@ -76,11 +79,13 @@ This guide will always assume you are starting with the latest TypeScript versio ## React + TypeScript Starter Kits -1. [Create React App v2.1+ with Typescript](https://facebook.github.io/create-react-app/docs/adding-typescript): `npm create react-app my-new-react-typescript-app --typescript` - - We used to recommend `create-react-app-typescript` but it is now [deprecated](https://www.reddit.com/r/reactjs/comments/a5919a/createreactapptypescript_has_been_archived_rip/). [see migration instructions](https://vincenttunru.com/migrate-create-react-app-typescript-to-create-react-app/) +1. [Create React App v2.1+ with Typescript](https://facebook.github.io/create-react-app/docs/adding-typescript): `npm create react-app my-new-react-typescript-app --typescript` + +- We used to recommend `create-react-app-typescript` but it is now [deprecated](https://www.reddit.com/r/reactjs/comments/a5919a/createreactapptypescript_has_been_archived_rip/). [see migration instructions](https://vincenttunru.com/migrate-create-react-app-typescript-to-create-react-app/) + 2. [Basarat's guide](https://github.com/basarat/typescript-react/tree/master/01%20bootstrap) for **manual setup** of React + TypeScript + Webpack + Babel -- In particular, make sure that you have `@types/react` and `@types/react-dom` installed ([Read more about the DefinitelyTyped project if you are unfamiliar](https://definitelytyped.org/)) +- In particular, make sure that you have `@types/react` and `@types/react-dom` installed ([Read more about the DefinitelyTyped project if you are unfamiliar](https://definitelytyped.org/)) - There are also many React + TypeScript boilerplates, please see [our Resources list below](https://github.com/sw-yx/react-typescript-cheatsheet#recommended-react--typescript-codebases-to-learn-from). ## Import React @@ -104,8 +109,8 @@ import ReactDOM from 'react-dom'; Why `allowSyntheticDefaultImports` over `esModuleInterop`? [Daniel Rosenwasser](https://twitter.com/drosenwasser/status/1003097042653073408) has said that it's better for webpack/parcel. For more discussion check out Please PR or [File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new) with your suggestions! - + # Section 2: Getting Started @@ -120,23 +125,26 @@ const App = ({ message }: { message: string }) =>
{message}
; Or you can use the provided generic type for function components: ```tsx -const App: React.FC<{ message: string }> = ({ message }) =>
{message}
; // React.FunctionComponent also works +const App: React.FC<{ message: string }> = ({ message }) => ( +
{message}
+); // React.FunctionComponent also works ```
What's the difference? -The former pattern is shorter, so why would people use `React.FunctionComponent` at all? +The former pattern is shorter, so why would people use `React.FunctionComponent` at all? -- If you need to use `children` property inside the function body, in the former case it has to be added explicitly. `FunctionComponent` already includes the correctly typed `children` property which then doesn't have to become part of your type. +- If you need to use `children` property inside the function body, in the former case it has to be added explicitly. `FunctionComponent` already includes the correctly typed `children` property which then doesn't have to become part of your type. - Typing your function explicitly will also give you typechecking and autocomplete on its static properties, like `displayName`, `propTypes`, and `defaultProps`. -- *In future*, it will also set `readonly` on your props just like `React.Component` does. +- _In future_, it will also set `readonly` on your props just like `React.Component` does. ```tsx -const Title: React.FunctionComponent<{ title: string }> = ({ children, title }) => ( -
{children}
-); +const Title: React.FunctionComponent<{ title: string }> = ({ + children, + title +}) =>
{children}
; ``` If you want to use the `function` keyword instead of an arrow function, you can use this syntax (using a function expression, instead of declaration): @@ -157,15 +165,16 @@ These patterns are not supported: **Conditional rendering** ```tsx -const MyConditionalComponent = ({ shouldRender = false }) => shouldRender ?
: false // don't do this in JS either -const el = // throws an error +const MyConditionalComponent = ({ shouldRender = false }) => + shouldRender ?
: false; // don't do this in JS either +const el = ; // throws an error ``` 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`. ```tsx -const MyArrayComponent = () => Array(5).fill(
) -const el2 = // throws an error +const MyArrayComponent = () => Array(5).fill(
); +const el2 = ; // throws an error ``` **Array.fill** @@ -173,21 +182,20 @@ const el2 = // throws an error 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: ```tsx -const MyArrayComponent = () => Array(5).fill(
) as any as JSX.Element +const MyArrayComponent = () => (Array(5).fill(
) as any) as JSX.Element; ``` [See commentary by @ferdaber here](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57).
- ## Hooks Hooks are supported in `@types/react` from v16.7 up. **useState** -Type inference works very well most of the time: +Type inference works very well most of the time: ```tsx const [val, toggle] = useState(false); // `val` is inferred to be a boolean, `toggle` only takes booleans @@ -201,7 +209,7 @@ However, many hooks are initialized with null-ish default values, and you may wo const [user, setUser] = useState(null); // later... -setUser(newUser) +setUser(newUser); ``` **Custom Hooks** @@ -232,25 +240,28 @@ Example React Hooks + TypeScript Libraries: [Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new). - ## Class Components Within TypeScript, `React.Component` is a generic type (aka `React.Component`), so you want to provide it with (optional) prop and state type parameters: ```tsx -type MyProps = { // using `interface` is also ok - message: string -} +type MyProps = { + // using `interface` is also ok + message: string; +}; type MyState = { - count: number // like this -} + count: number; // like this +}; class App extends React.Component { - state: MyState = { // optional second annotation for better type inference + state: MyState = { + // optional second annotation for better type inference count: 0 - } + }; render() { return ( -
{this.props.message} {this.state.count}
+
+ {this.props.message} {this.state.count} +
); } } @@ -261,7 +272,7 @@ Don't forget that you can export/import/extend these types/interfaces for reuse.
Why annotate `state` twice? -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. +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. @@ -269,7 +280,6 @@ This is because they work in two different ways, the 2nd generic type parameter
-
No need for readonly @@ -277,37 +287,35 @@ You often see sample code include `readonly` to mark props and state immutable: ```tsx type MyProps = { - readonly message: string -} + readonly message: string; +}; type MyState = { - readonly count: number -} + readonly count: number; +}; ``` This is not necessary as `React.Component` already marks them as immutable. ([See PR and discussion!](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26813))
- - **Class Methods**: Do it like normal, but just remember any arguments for your functions also need to be typed: ```tsx -class App extends React.Component< - { message: string }, - { count: number } - > { - state = { count: 0 } +class App extends React.Component<{ message: string }, { count: number }> { + state = { count: 0 }; render() { return ( -
this.increment(1)}>{this.props.message} {this.state.count}
+
this.increment(1)}> + {this.props.message} {this.state.count} +
); } - increment = (amt: number) => { // like this + increment = (amt: number) => { + // like this this.setState(state => ({ count: state.count + amt })); - } + }; } ``` @@ -315,15 +323,17 @@ class App extends React.Component< ```tsx class App extends React.Component<{ - message: string, + message: string; }> { - pointer: number // like this + pointer: number; // like this componentDidMount() { this.pointer = 3; } render() { return ( -
{this.props.message} and {this.pointer}
+
+ {this.props.message} and {this.pointer} +
); } } @@ -337,19 +347,19 @@ For Typescript 3.0+, type inference [should work](https://www.typescriptlang.org ```tsx export type Props = { - name: string; -} + name: string; +}; export class Greet extends React.Component { - render() { - const { name } = this.props; - return
Hello ${name.toUpperCase()}!
; - } - static defaultProps = { name: "world"}; + render() { + const { name } = this.props; + return
Hello ${name.toUpperCase()}!
; + } + static defaultProps = { name: 'world' }; } // Type-checks! No type assertions needed! -let el = +let el = ; ```
@@ -358,12 +368,14 @@ let el = For Typescript 2.9 and earlier, there's more than one way to do it, but this is the best advice we've yet seen: ```ts -type Props = Required & { /* additional props here */ } +type Props = Required & { + /* additional props here */ +}; export class MyComponent extends React.Component { static defaultProps = { foo: 'foo' - } + }; } ``` @@ -377,7 +389,7 @@ interface IMyComponentProps { export class MyComponent extends React.Component { public static defaultProps: Partial = { - firstProp: "default", + firstProp: 'default' }; } ``` @@ -404,7 +416,7 @@ Types are useful for union types (e.g. `type MyType = TypeA | TypeB`) whereas In Useful table for Types vs Interfaces -It's a nuanced topic, don't get too hung up on it. Here's a handy graphic: +It's a nuanced topic, don't get too hung up on it. Here's a handy graphic: ![https://pbs.twimg.com/media/DwV-oOsXcAIct2q.jpg](https://pbs.twimg.com/media/DwV-oOsXcAIct2q.jpg) (source: [Karol Majewski](https://twitter.com/karoljmajewski/status/1082413696075382785)) @@ -416,35 +428,35 @@ It's a nuanced topic, don't get too hung up on it. Here's a handy graphic: ```tsx type AppProps = { - message: string, - count: number, - disabled: boolean, + message: string; + count: number; + disabled: boolean; /** array of a type! */ - names: string[], + names: string[]; /** string literals to specify exact string values, with a union type to join them together */ - status: 'waiting' | 'success', + status: 'waiting' | 'success'; /** any object as long as you dont use its properties (not common) */ - obj: object, - obj2: {}, // same + obj: object; + obj2: {}; // same /** an object with defined properties (preferred) */ obj3: { - id: string, - title: string - }, + id: string; + title: string; + }; /** array of objects! (common) */ objArr: { - id: string, - title: string - }[], + id: string; + title: string; + }[]; /** any function as long as you don't invoke it (not recommended) */ - onSomething: Function, + onSomething: Function; /** function that doesn't take or return anything (VERY COMMON) */ - onClick: () => void, + onClick: () => void; /** function with named prop (VERY COMMON) */ - onChange: (id: number) => void, + onChange: (id: number) => void; /** an optional prop (VERY COMMON!) */ - optional?: OptionalType, -} + optional?: OptionalType; +}; ``` Notice we have used the TSDoc `/** comment */` style here on each prop. You can and are encouraged to leave descriptive comments on reusable components. For a fuller example and discussion, see our [Commenting Components](/ADVANCED.md#commenting-components) section in the Advanced Cheatsheet. @@ -453,13 +465,13 @@ Notice we have used the TSDoc `/** comment */` style here on each prop. You can ```tsx export declare interface AppProps { - children1: JSX.Element; // bad, doesnt account for arrays - children2: JSX.Element | JSX.Element[]; // meh, doesnt accept functions + children1: JSX.Element; // bad, doesnt account for arrays + children2: JSX.Element | JSX.Element[]; // meh, doesnt accept functions children3: React.ReactChild | React.ReactChildren; // better, but doesnt accept strings - children: React.ReactNode; // best, accepts everything - style?: React.CSSProperties; // to pass through style props + children: React.ReactNode; // best, accepts everything + style?: React.CSSProperties; // to pass through style props onChange?: React.FormEventHandler; // form events! the generic parameter is the type of event.target - props: Props & React.PropsWithoutRef // to impersonate all the props of a button element without its ref + props: Props & React.PropsWithoutRef; // to impersonate all the props of a button element without its ref } ``` @@ -468,9 +480,9 @@ export declare interface AppProps { Quote [@ferdaber](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57): A more technical explanation is that a valid React node is not the same thing as what is returned by `React.createElement`. Regardless of what a component ends up rendering, `React.createElement` always returns an object, which is the `JSX.Element` interface, but `React.ReactNode` is the set of all possible return values of a component. -* `JSX.Element` -> Return value of `React.createElement` -* `React.ReactNode` -> Return value of a component -
+- `JSX.Element` -> Return value of `React.createElement` +- `React.ReactNode` -> Return value of a component + [Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new). @@ -479,38 +491,44 @@ Quote [@ferdaber](https://github.com/sw-yx/react-typescript-cheatsheet/issues/57 If performance is not an issue, inlining handlers is easiest as you can just use type inference: ```tsx -const el = -)) +)); ``` More info: https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315 @@ -616,22 +632,19 @@ const modalRoot = document.getElementById('modal-root') as HTMLElement; // assuming in your html file has a div with id 'modal-root'; export class Modal extends React.Component { - el: HTMLElement = document.createElement('div'); + el: HTMLElement = document.createElement('div'); - componentDidMount() { - modalRoot.appendChild(this.el); - } + componentDidMount() { + modalRoot.appendChild(this.el); + } - componentWillUnmount() { - modalRoot.removeChild(this.el); - } + componentWillUnmount() { + modalRoot.removeChild(this.el); + } - render() { - return ReactDOM.createPortal( - this.props.children, - this.el - ) - } + render() { + return ReactDOM.createPortal(this.props.children, this.el); + } } ``` @@ -645,19 +658,19 @@ This example is based on the [Event Bubbling Through Portal](https://reactjs.org ## Error Boundaries -*Not written yet.* +_Not written yet._ [Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new). ## Concurrent React/React Suspense -*Not written yet.* watch for more on React Suspense and Time Slicing. +_Not written yet._ watch for more on React Suspense and Time Slicing. [Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new). # Basic Troubleshooting Handbook: Types -Facing weird type errors? You aren't alone. This is the hardest part of using TypeScript with React. Be patience - you are learning a new language after all. However, the more you get good at this, the less time you'll be working *against* the compiler and the more the compiler will be working *for* you! +Facing weird type errors? You aren't alone. This is the hardest part of using TypeScript with React. Be patience - you are learning a new language after all. However, the more you get good at this, the less time you'll be working _against_ the compiler and the more the compiler will be working _for_ you! Try to avoid typing with `any` as much as possible to experience the full benefits of typescript. Instead, let's try to be familiar with some of the common strategies to solve these issues. @@ -666,22 +679,23 @@ Try to avoid typing with `any` as much as possible to experience the full benefi Union types are handy for solving some of these typing problems: ```tsx -class App extends React.Component<{}, { - count: number | null, // like this - }> { +class App extends React.Component< + {}, + { + count: number | null; // like this + } +> { state = { count: null - } + }; render() { - return ( -
this.increment(1)}>{this.state.count}
- ); + return
this.increment(1)}>{this.state.count}
; } increment = (amt: number) => { this.setState(state => ({ count: (state.count || 0) + amt })); - } + }; } ``` @@ -718,20 +732,18 @@ If a component has an optional prop, add a question mark and assign during destr ```tsx class MyComponent extends React.Component<{ - message?: string, // like this + message?: string; // like this }> { render() { - const {message = 'default'} = this.props; - return ( -
{message}
- ); + const { message = 'default' } = this.props; + return
{message}
; } } ``` You can also use a `!` character to assert that something is not undefined, but this is not encouraged. -*Something to add? [File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new) with your suggestions!* +_Something to add? [File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new) with your suggestions!_ ## Enum Types @@ -750,12 +762,7 @@ Usage: ```tsx export const PrimaryButton = ( props: Props & React.HTMLProps -) => ( -