From 520c2c153366bd9c691ae887a4c55322dc1371a8 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 2 May 2019 16:56:09 +0200 Subject: [PATCH 1/5] Add prettier --- .gitignore | 1 + package.json | 8 ++++++-- yarn.lock | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..40b878db --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/package.json b/package.json index d034df10..5016b1ad 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "react-typescript-cheatsheet", "version": "1.0.0", - "description": "this package.json is just for all-contributors to work", + "description": "this package.json is just for maintenance work", "main": "index.js", "scripts": { + "format": "prettier --write \"**/*.md\"", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -16,5 +17,8 @@ "bugs": { "url": "https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues" }, - "homepage": "https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#readme" + "homepage": "https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#readme", + "devDependencies": { + "prettier": "^1.17.0" + } } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..0619ba57 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +prettier@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008" + integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw== From 9207c083e1e9dd33de99147e807187ce20f0fad5 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 2 May 2019 16:59:33 +0200 Subject: [PATCH 2/5] Use prettier config closest to current formatting --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 5016b1ad..bd16427b 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,8 @@ "homepage": "https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#readme", "devDependencies": { "prettier": "^1.17.0" + }, + "prettier": { + "singleQuote": true } } From d87fced965562355634f5c0379d6802226d51a47 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 2 May 2019 16:59:37 +0200 Subject: [PATCH 3/5] Use prettier --- .github/ISSUE_TEMPLATE/advanced-cheatsheet.md | 5 +- .github/ISSUE_TEMPLATE/basic-cheatsheet.md | 5 +- .../general-react-ts-question.md | 5 +- .github/ISSUE_TEMPLATE/hoc-cheatsheet.md | 5 +- .../ISSUE_TEMPLATE/migrating-cheatsheet.md | 5 +- ADVANCED.md | 29 ++- CONTRIBUTORS.md | 4 +- MIGRATING.md | 3 +- README.md | 179 ++++++++++-------- 9 files changed, 121 insertions(+), 119 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/advanced-cheatsheet.md b/.github/ISSUE_TEMPLATE/advanced-cheatsheet.md index 285145f3..91e8610b 100644 --- a/.github/ISSUE_TEMPLATE/advanced-cheatsheet.md +++ b/.github/ISSUE_TEMPLATE/advanced-cheatsheet.md @@ -1,10 +1,9 @@ --- name: Advanced Cheatsheet about: Report Issue/Suggest an idea for Advanced Cheatsheet -title: "[Advanced] ISSUE_TITLE_HERE" +title: '[Advanced] ISSUE_TITLE_HERE' labels: ADVANCED assignees: '' - --- **What cheatsheet is this about? (if applicable)** @@ -13,4 +12,4 @@ Advanced cheatsheet **What's your issue or idea?** -*Write here* +_Write here_ diff --git a/.github/ISSUE_TEMPLATE/basic-cheatsheet.md b/.github/ISSUE_TEMPLATE/basic-cheatsheet.md index 12a2df76..98cc7abc 100644 --- a/.github/ISSUE_TEMPLATE/basic-cheatsheet.md +++ b/.github/ISSUE_TEMPLATE/basic-cheatsheet.md @@ -1,10 +1,9 @@ --- name: Basic Cheatsheet about: Report Issue/Suggest an idea for Basic Cheatsheet -title: "[Basic] ISSUE_TITLE_HERE" +title: '[Basic] ISSUE_TITLE_HERE' labels: BASIC assignees: sw-yx - --- **What cheatsheet is this about? (if applicable)** @@ -13,4 +12,4 @@ Basic cheatsheet **What's your issue or idea?** -*Write here* +_Write here_ diff --git a/.github/ISSUE_TEMPLATE/general-react-ts-question.md b/.github/ISSUE_TEMPLATE/general-react-ts-question.md index e73563b0..2984f346 100644 --- a/.github/ISSUE_TEMPLATE/general-react-ts-question.md +++ b/.github/ISSUE_TEMPLATE/general-react-ts-question.md @@ -1,10 +1,7 @@ --- name: General React+TS Question about: Questions are welcome! We want to know and solve your pain points. -title: "[Question] QUESTION_TITLE_HERE" +title: '[Question] QUESTION_TITLE_HERE' labels: good first issue assignees: sw-yx - --- - - diff --git a/.github/ISSUE_TEMPLATE/hoc-cheatsheet.md b/.github/ISSUE_TEMPLATE/hoc-cheatsheet.md index 9b6407fe..b7dde203 100644 --- a/.github/ISSUE_TEMPLATE/hoc-cheatsheet.md +++ b/.github/ISSUE_TEMPLATE/hoc-cheatsheet.md @@ -1,10 +1,9 @@ --- name: HOC Cheatsheet about: Report Issue/Suggest an idea for HOC Cheatsheet -title: "[HOC] ISSUE_TITLE_HERE" +title: '[HOC] ISSUE_TITLE_HERE' labels: HOC assignees: '' - --- **What cheatsheet is this about? (if applicable)** @@ -13,4 +12,4 @@ HOC cheatsheet **What's your issue or idea?** -*Write here* +_Write here_ diff --git a/.github/ISSUE_TEMPLATE/migrating-cheatsheet.md b/.github/ISSUE_TEMPLATE/migrating-cheatsheet.md index 458f5c77..b2056d1c 100644 --- a/.github/ISSUE_TEMPLATE/migrating-cheatsheet.md +++ b/.github/ISSUE_TEMPLATE/migrating-cheatsheet.md @@ -1,10 +1,9 @@ --- name: Migrating Cheatsheet about: Report Issue/Suggest an idea for Migrating Cheatsheet -title: "[Migrating] ISSUE_TITLE_HERE" +title: '[Migrating] ISSUE_TITLE_HERE' labels: MIGRATING assignees: '' - --- **What cheatsheet is this about? (if applicable)** @@ -13,4 +12,4 @@ Migrating cheatsheet **What's your issue or idea?** -*Write here* +_Write here_ diff --git a/ADVANCED.md b/ADVANCED.md index a33d81cc..8b657ba7 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -378,11 +378,11 @@ You want to allow `expanded` to be passed only if `truncate` is also passed, bec You can do this by function overloads: ```tsx -import React from "react"; +import React from 'react'; type CommonProps = { children: React.ReactNode; - as: "p" | "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + as: 'p' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; }; type NoTruncateProps = CommonProps & { @@ -403,18 +403,13 @@ const isTruncateProps = ( function Text(props: NoTruncateProps): JSX.Element; function Text(props: TruncateProps): JSX.Element; function Text(props: NoTruncateProps | TruncateProps) { - if (isTruncateProps(props)) { const { children, as: Tag, truncate, expanded, ...otherProps } = props; - const classNames = truncate ? ".truncate" : ""; + const classNames = truncate ? '.truncate' : ''; return ( - + {children} ); @@ -427,17 +422,20 @@ function Text(props: NoTruncateProps | TruncateProps) { Text.defaultProps = { as: 'span' -} +}; ``` Using the Text component: + ```tsx const App: React.FC = () => ( <> not truncated {/* works */} truncated {/* works */} - truncate-able but expanded {/* works */} - + + truncate-able but expanded + + {/* works */} {/* TS error: Property 'truncate' is missing in type '{ children: string; expanded: true; }' but required in type 'Pick'} */} truncate-able but expanded @@ -676,7 +674,7 @@ export function useLoading() { setState(true); return aPromise.finally(() => setState(false)); }; - return [isLoading, load] as const // infers [boolean, typeof load] instead of (boolean | typeof load)[] + return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[] } ``` @@ -852,7 +850,6 @@ This is taken from [the `tsdx` PR](https://github.com/palmerhq/tsdx/pull/70/file More `.eslintrc.json` options to consider with more options you may want for **apps**: ```json - { "extends": [ "airbnb", @@ -910,8 +907,8 @@ So create a `.d.ts` file anywhere in your project with the module definition: ```ts // de-indent.d.ts declare module 'de-indent' { - function deindent(): void - export = deindent // default export + function deindent(): void; + export = deindent; // default export } ``` diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ae7fd052..46670c0f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,4 +1,3 @@ - ## Contributors @@ -6,10 +5,11 @@
Ferdy Budhidharma
Ferdy Budhidharma

👀 🚧 🖋
swyx
swyx

🤔 👀 🚧 🖋 💬
Sebastian Silbermann
Sebastian Silbermann

👀 🚧 🖋
Islam Attrash
Islam Attrash

🚧 🖋
Stephen Koo
Stephen Koo

💬 💡
+ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/MIGRATING.md b/MIGRATING.md index 0141850d..8980089a 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -54,7 +54,6 @@ Misc tips/approaches successful companies have taken - pick ESLint over TSLint (source: [ESLint](https://eslint.org/blog/2019/01/future-typescript-eslint) and [TS Roadmap](https://github.com/Microsoft/TypeScript/issues/29288)) - 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]) -
@@ -109,7 +108,7 @@ Problems to be aware of: - 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*) +(_thanks [Gil Tayar](https://twitter.com/giltayar/status/1089228919260221441) and [Gleb Bahmutov](https://twitter.com/bahmutov/status/1089229196247908353) for sharing above commentary_) ## From JS diff --git a/README.md b/README.md index 0dec2c34..6f5888c4 100644 --- a/README.md +++ b/README.md @@ -247,8 +247,8 @@ setUser(newUser); When using `useRef`, you have two options when creating a ref container that does not have an initial value: ```ts -const ref1 = useRef(null!) -const ref2 = useRef(null) +const ref1 = useRef(null!); +const ref2 = useRef(null); ``` The first option will make `ref1.current` read-only, and is intended to be passed in to built-in `ref` attributes that React will manage (because React handles setting the `current` value for you). @@ -263,8 +263,14 @@ When using `useEffect`, take care not to return anything other than a function o function DelayedEffect(props: { timerMs: number }) { const { timerMs } = props; // bad! setTimeout implicitly returns a number because the arrow function body isn't wrapped in curly braces - useEffect(() => setTimeout(() => {/* do stuff */}, timerMs), [timerMs]) - return null + useEffect( + () => + setTimeout(() => { + /* do stuff */ + }, timerMs), + [timerMs] + ); + return null; } ``` @@ -278,13 +284,13 @@ function TextInputWithFocusButton() { // strict null checks need us to check if inputEl and current exist. // but once current exists, it is of type HTMLInputElement, thus it // has the method focus! ✅ - if(inputEl && inputEl.current) { + if (inputEl && inputEl.current) { inputEl.current.focus(); - } + } }; return ( <> - { /* in addition, inputEl only can be used with input elements. Yay! */ } + {/* in addition, inputEl only can be used with input elements. Yay! */} @@ -300,8 +306,8 @@ You can use [Discriminated Unions](https://www.typescriptlang.org/docs/handbook/ ```tsx type Action = - { type: 'SET_ONE'; payload: string; } - | { type: 'SET_TWO'; payload: number; }; + | { type: 'SET_ONE'; payload: string } + | { type: 'SET_TWO'; payload: number }; export function reducer(state: AppState, action: Action): AppState { switch (action.type) { @@ -325,7 +331,6 @@ export function reducer(state: AppState, action: Action): AppState { If you are returning an array in your Custom Hook, you will want to avoid type inference as Typescript will infer a union type (when you actually want different types in each position of the array). Instead, use [TS 3.4 const assertions](https://devblogs.microsoft.com/typescript/announcing-typescript-3-4/#const-assertions): - ```tsx export function useLoading() { const [isLoading, setState] = React.useState(false); @@ -333,9 +338,9 @@ export function useLoading() { setState(true); return aPromise.finally(() => setState(false)); }; - return [isLoading, load] as const // infers [boolean, typeof load] instead of (boolean | typeof load)[] + return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[] } -``` +``` This way, when you destructure you actually get the right types based on destructure position. @@ -356,23 +361,25 @@ export function useLoading() { (aPromise: Promise) => Promise ]; } -``` - +``` A helper function that automatically types tuples can also be helpful if you write a lot of custom hooks: + ```ts -function tuplify(...elements: T) { return elements } +function tuplify(...elements: T) { + return elements; +} function useArray() { - const numberValue = useRef(3).current - const functionValue = useRef(() => {}).current - return [numberValue, functionValue] // type is (number | (() => void))[] + const numberValue = useRef(3).current; + const functionValue = useRef(() => {}).current; + return [numberValue, functionValue]; // type is (number | (() => void))[] } function useTuple() { - const numberValue = useRef(3).current - const functionValue = useRef(() => {}).current - return tuplify(numberValue, functionValue) // type is [number, () => void] + const numberValue = useRef(3).current; + const functionValue = useRef(() => {}).current; + return tuplify(numberValue, functionValue); // type is [number, () => void] } ``` @@ -506,42 +513,43 @@ For Typescript 3.0+, type inference [should work](https://www.typescriptlang.org // //////////////// type Props = { age: number } & typeof defaultProps; const defaultProps = { - who: 'Johny Five', + who: 'Johny Five' }; const Greet = (props: Props) => { /*...*/ }; -Greet.defaultProps = defaultProps +Greet.defaultProps = defaultProps; ``` For **Class components**, there are [a couple ways to do it](https://github.com/sw-yx/react-typescript-cheatsheet/pull/103#issuecomment-481061483)(including using the `Pick` utility type) but the recommendation is to "reverse" the props definition: ```tsx type GreetProps = typeof Greet.defaultProps & { - age: number -} + age: number; +}; class Greet extends React.Component { static defaultProps = { name: 'world' - } + }; /*...*/ } // Type-checks! No type assertions needed! let el = ; ``` +
Why does React.FC break defaultProps? - You can check the discussions here: +You can check the discussions here: - - https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680 - - https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695 - - https://github.com/sw-yx/react-typescript-cheatsheet/issues/87 +- https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680 +- https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695 +- https://github.com/sw-yx/react-typescript-cheatsheet/issues/87 - This is just the current state and may be fixed in future. +This is just the current state and may be fixed in future.
@@ -677,7 +685,8 @@ If performance is not an issue, inlining handlers is easiest as you can just use ```tsx const el = ( -
- [Something to add? File an issue](https://github.com/sw-yx/react-typescript-cheatsheet/issues/new). ## forwardRef/createRef - Check the [Hooks section](https://github.com/sw-yx/react-typescript-cheatsheet/blob/master/README.md) for `useRef`. `createRef`: @@ -917,7 +929,6 @@ export const FancyButton = React.forwardRef((props, ref) => ( )); ``` - If you are grabbing the props of a component that forwards refs, use [`ComponentPropsWithRef`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L735). More info: https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315 @@ -1188,6 +1199,7 @@ partialStateUpdate({ foo: 2 }); // this works Note that there are some TS users who don't agree with using `Partial` as it behaves today. See [subtle pitfalls of the above example here](https://twitter.com/ferdaber/status/1084798596027957248), and check out this long discussion on [why @types/react uses Pick instead of Partial](https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365). + ## The Types I need weren't exported! @@ -1218,6 +1230,7 @@ function foo(bar: string) { // inside your app, if you need { baz: number } type FooReturn = ReturnType; // { baz: number } ``` + # Troubleshooting Handbook: Images and other non-TS/TSX files Use [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html): @@ -1225,10 +1238,10 @@ Use [declaration merging](https://www.typescriptlang.org/docs/handbook/declarati ```ts // declaration.d.ts // anywhere in your project, NOT the same name as any of your .ts/tsx files -declare module '*.png' +declare module '*.png'; // importing in a tsx file -import * as logo from "./logo.png"; +import * as logo from './logo.png'; ``` Related issue: https://github.com/Microsoft/TypeScript-React-Starter/issues/12 and [StackOverflow](https://stackoverflow.com/a/49715468/4216035) From f0b3cfd60f1459c1ec54b2d27289eb7f856ff269 Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Tue, 14 May 2019 22:11:04 +0200 Subject: [PATCH 4/5] Fix syntax error --- HOC.md | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/HOC.md b/HOC.md index 6ea5a740..3b07db72 100644 --- a/HOC.md +++ b/HOC.md @@ -356,33 +356,38 @@ function Dog({name, owner}: DogProps) { And we have a `withOwner` HOC that injects the `owner`: ```tsx -const OwnedDog = withOwner('swyx')(Dog) +const OwnedDog = withOwner('swyx')(Dog); ``` We want to type `withOwner` such that it will pass through the types of any component like `Dog`, into the type of `OwnedDog`, minus the `owner` property it injects: ```tsx -typeof OwnedDog // we want this to be equal to { name: string } +typeof OwnedDog; // we want this to be equal to { name: string } - // this should be fine - // this should have a typeError - // this should be fine +; // this should be fine +; // this should have a typeError +; // this should be fine // and the HOC should be reusable for completely different prop types! -type CatProps { - lives: number - owner: string -} -function Cat({lives, owner}: CatProps) { - return
Meow: {lives}, Owner: {owner}
+type CatProps = { + lives: number; + owner: string; +}; +function Cat({ lives, owner }: CatProps) { + return ( +
+ {' '} + Meow: {lives}, Owner: {owner} +
+ ); } -const OwnedCat = withOwner('swyx')(Cat) +const OwnedCat = withOwner('swyx')(Cat); - // this should be fine - // this should have a typeError - // this should be fine +; // this should be fine +; // this should have a typeError +; // this should be fine ``` So how do we type `withOwner`? @@ -396,16 +401,17 @@ So how do we type `withOwner`? ```tsx function withOwner(owner: string) { - return function (Component: React.ComponentType) { - return function (props: Omit): React.ReactNode { - return - } - } + return function( + Component: React.ComponentType + ) { + return function(props: Omit): React.ReactNode { + return ; + }; + }; } ``` -*Note: above is an incomplete, nonworking example. PR a fix!* - +_Note: above is an incomplete, nonworking example. PR a fix!_ ## Good articles From c1b58924e96b059650c0936f4f11e88da4211d7c Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Tue, 14 May 2019 22:12:43 +0200 Subject: [PATCH 5/5] Format rebase --- ADVANCED.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ADVANCED.md b/ADVANCED.md index 8b657ba7..74bcf44f 100644 --- a/ADVANCED.md +++ b/ADVANCED.md @@ -890,7 +890,7 @@ More `.eslintrc.json` options to consider with more options you may want for **a } ``` -You can read a [fuller TypeScript + ESLint setup guide here](https://github.com/MatterhornDev/matterhorn-posts/blob/learn-typescript-linting/learn-typescript-linting.md) from Matterhorn, in particular check https://github.com/MatterhornDev/learn-typescript-linting. +You can read a [fuller TypeScript + ESLint setup guide here](https://github.com/MatterhornDev/matterhorn-posts/blob/learn-typescript-linting/learn-typescript-linting.md) from Matterhorn, in particular check https://github.com/MatterhornDev/learn-typescript-linting. ## Working with Non-TypeScript Libraries (writing your own index.d.ts) diff --git a/README.md b/README.md index 6f5888c4..5a0c867b 100644 --- a/README.md +++ b/README.md @@ -1149,7 +1149,7 @@ export const Human: React.FC = // ... export const Dog: React.FC = // ... ``` -Make sure not to confuse Intersection Types (which are **and** operations) with Union Types (which are **or** operations). +Make sure not to confuse Intersection Types (which are **and** operations) with Union Types (which are **or** operations). ## Union Types