diff --git a/README.md b/README.md index 70800d68..f4dd5048 100644 --- a/README.md +++ b/README.md @@ -2405,6 +2405,7 @@ It is worth mentioning some resources to help you get started: - Marius Schultz: https://blog.mariusschulz.com/series/typescript-evolution with an [Egghead.io course](https://egghead.io/courses/advanced-static-types-in-typescript) - Basarat's Deep Dive: https://basarat.gitbook.io/typescript/ - Rares Matei: [Egghead.io course](https://egghead.io/courses/practical-advanced-typescript)'s advanced TypeScript course on Egghead.io is great for newer typescript features and practical type logic applications (e.g. recursively making all properties of a type `readonly`) +- Go through [Remo Jansen's TypeScript ladder](http://www.techladder.io/?tech=typescript) - Shu Uesugi: [TypeScript for Beginner Programmers](https://ts.chibicode.com/) diff --git a/docs/advanced/patterns_by_usecase.md b/docs/advanced/patterns_by_usecase.md index 2a2b59b5..6355f9ef 100644 --- a/docs/advanced/patterns_by_usecase.md +++ b/docs/advanced/patterns_by_usecase.md @@ -315,6 +315,24 @@ type NumbersChildren = number[]; type TwoNumbersChildren = [number, number]; ``` +
+ +Don't forget that you can also use `prop-types` if TS fails you. + + +```ts +Parent.propTypes = { + children: PropTypes.shape({ + props: PropTypes.shape({ + // could share `propTypes` to the child + value: PropTypes.string.isRequired, + }), + }).isRequired, +}; +``` + +
+ ### What You CANNOT Do The thing you cannot do is **specify which components** the children are, e.g. If you want to express the fact that "React Router `` can only have `` as children, nothing else is allowed" in TypeScript. diff --git a/docs/basic/getting-started/default-props.md b/docs/basic/getting-started/default-props.md index f0364890..7bb7c944 100644 --- a/docs/basic/getting-started/default-props.md +++ b/docs/basic/getting-started/default-props.md @@ -65,7 +65,7 @@ const Greet = ({ age = 21 }: GreetProps) => { // class components // //////////////// type GreetProps = { - age: number; + age?: number; }; class Greet extends React.Component { @@ -125,7 +125,7 @@ export class MyComponent extends React.Component { 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. -[See commentary by @ferdaber here](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/57). +[See commentary by @ferdaber here](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/issues/57) and [here](https://github.com/typescript-cheatsheets/react/issues/61). diff --git a/docs/basic/getting-started/hooks.md b/docs/basic/getting-started/hooks.md index 4fd6986f..04116d58 100644 --- a/docs/basic/getting-started/hooks.md +++ b/docs/basic/getting-started/hooks.md @@ -5,7 +5,7 @@ title: Hooks Hooks are [supported in `@types/react` from v16.8 up](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/a05cc538a42243c632f054e42eab483ebf1560ab/types/react/index.d.ts#L800-L1031). -**useState** +## useState Type inference works very well most of the time: @@ -24,37 +24,53 @@ const [user, setUser] = React.useState(null); setUser(newUser); ``` -**useRef** +## useReducer -When using `useRef`, you have two options when creating a ref container that does not have an initial value: +You can use [Discriminated Unions](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) for reducer actions. Don't forget to define the return type of reducer, otherwise TypeScript will infer it. -```ts -const ref1 = useRef(null!); -const ref2 = useRef(null); +```tsx +type AppState = {}; +type Action = + | { type: "SET_ONE"; payload: string } // typescript union types allow for leading |'s to have nicer layout + | { type: "SET_TWO"; payload: number }; + +export function reducer(state: AppState, action: Action): AppState { + switch (action.type) { + case "SET_ONE": + return { + ...state, + one: action.payload, // `payload` is string + }; + case "SET_TWO": + return { + ...state, + two: action.payload, // `payload` is number + }; + default: + return state; + } +} ``` -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). +[View in the TypeScript Playground](https://www.typescriptlang.org/play/?jsx=2#code/C4TwDgpgBAgmYGVgENjQLxQN4F8CwAUKJLAMbACWA9gHZTqFRQA+2UxEAXFAEQICiAFQD6AeQBy-HgG4oYZCAA2VZABNuAZ2AAnCjQDmUfASass7cF14CRggOqiZchcrXcaAVwC2AIwjajaUJCCAAPMCptYCgAMw8acmo6bQhVD1J-AAotVCs4RBQ0ABooZETabhhymgBKSvgkXOxGKA0AdwpgUgALKEyyyloAOg4a5pMmKFJkDWg+ITFJHk4WyagU4A9tOixVtaghw5zivbXaKwGkofklFVUoAHoHqAADG9dVF6gKDVadPX0p0Ce2ms2sC3sjhWEzWGy2OyBTEOQ2OECKiPYbSo3Euw3ed0ezzeLjuXx+UE8vn8QJwQRhUFUEBiyA8imA0P26wgm22f1ydKYxhwQA)
- What is the ! at the end of null!? -`null!` is a non-null assertion operator (the `!`). It asserts that any expression before it is not `null` or `undefined`, so if you have `useRef(null!)` it means that you're instantiating the ref with a current value of `null` but lying to TypeScript that it's not `null`. +Usage with `Reducer` from `redux` -```ts -function MyComponent() { - const ref1 = useRef(null!); - useEffect(() => { - doSomethingWith(ref1.current); // TypeScript won't require null-check e.g. ref1 && ref1.current - }); - return
etc
; -} +In case you use the [redux](https://github.com/reduxjs/redux) library to write reducer function, It provides a convenient helper of the format `Reducer` which takes care of the return type for you. + +So the above reducer example becomes: + +```tsx +import { Reducer } from 'redux'; + +export function reducer: Reducer() {} ```
-The second option will make `ref2.current` mutable, and is intended for "instance variables" that you manage yourself. - -**useEffect** +## useEffect When using `useEffect`, take care not to return anything other than a function or `undefined`, otherwise both TypeScript and React will yell at you. This can be subtle when using arrow functions: @@ -73,7 +89,35 @@ function DelayedEffect(props: { timerMs: number }) { } ``` -**useRef** +## useRef + +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); +``` + +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). + +
+ What is the ! at the end of null!? + +`null!` is a non-null assertion operator (the `!`). It asserts that any expression before it is not `null` or `undefined`, so if you have `useRef(null!)` it means that you're instantiating the ref with a current value of `null` but lying to TypeScript that it's not `null`. + +```ts +function MyComponent() { + const ref1 = useRef(null!); + useEffect(() => { + doSomethingWith(ref1.current); // TypeScript won't require null-check e.g. ref1 && ref1.current + }); + return
etc
; +} +``` + +
+ +The second option will make `ref2.current` mutable, and is intended for "instance variables" that you manage yourself. ```tsx function TextInputWithFocusButton() { @@ -101,53 +145,25 @@ function TextInputWithFocusButton() { example from [Stefan Baumgartner](https://fettblog.eu/typescript-react/hooks/#useref) -**useReducer** +## useImperativeHandle -You can use [Discriminated Unions](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) for reducer actions. Don't forget to define the return type of reducer, otherwise TypeScript will infer it. +_we dont have much here, but this is from [a discussion in our issues](https://github.com/typescript-cheatsheets/react/issues/106)_ ```tsx -type AppState = {}; -type Action = - | { type: "SET_ONE"; payload: string } // typescript union types allow for leading |'s to have nicer layout - | { type: "SET_TWO"; payload: number }; - -export function reducer(state: AppState, action: Action): AppState { - switch (action.type) { - case "SET_ONE": - return { - ...state, - one: action.payload, // `payload` is string - }; - case "SET_TWO": - return { - ...state, - two: action.payload, // `payload` is number - }; - default: - return state; - } +type ListProps = { + items: ItemType[]; + innerRef?: React.Ref<{ scrollToItem(item: ItemType): void }>; +}; + +function List(props: ListProps) { + useImperativeHandle(props.innerRef, () => ({ + scrollToItem() {}, + })); + return null; } ``` -[View in the TypeScript Playground](https://www.typescriptlang.org/play/?jsx=2#code/C4TwDgpgBAgmYGVgENjQLxQN4F8CwAUKJLAMbACWA9gHZTqFRQA+2UxEAXFAEQICiAFQD6AeQBy-HgG4oYZCAA2VZABNuAZ2AAnCjQDmUfASass7cF14CRggOqiZchcrXcaAVwC2AIwjajaUJCCAAPMCptYCgAMw8acmo6bQhVD1J-AAotVCs4RBQ0ABooZETabhhymgBKSvgkXOxGKA0AdwpgUgALKEyyyloAOg4a5pMmKFJkDWg+ITFJHk4WyagU4A9tOixVtaghw5zivbXaKwGkofklFVUoAHoHqAADG9dVF6gKDVadPX0p0Ce2ms2sC3sjhWEzWGy2OyBTEOQ2OECKiPYbSo3Euw3ed0ezzeLjuXx+UE8vn8QJwQRhUFUEBiyA8imA0P26wgm22f1ydKYxhwQA) - -
- -Usage with `Reducer` from `redux` - -In case you use the [redux](https://github.com/reduxjs/redux) library to write reducer function, It provides a convenient helper of the format `Reducer` which takes care of the return type for you. - -So the above reducer example becomes: - -```tsx -import { Reducer } from 'redux'; - -export function reducer: Reducer() {} -``` - -
- -**Custom Hooks** +## Custom Hooks 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): diff --git a/docs/hoc/full-example.md b/docs/hoc/full-example.md index 5a6ae49c..74e64860 100644 --- a/docs/hoc/full-example.md +++ b/docs/hoc/full-example.md @@ -92,4 +92,4 @@ export function inject( ### Using `forwardRef` -For "true" reusability you should also consider exposing a ref for your HOC. You can use `React.forwardRef` as documented in [the basic cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/README.md#forwardrefcreateref), but we are interested in more real world examples. [Here is a nice example in practice](https://gist.github.com/OliverJAsh/d2f462b03b3e6c24f5588ca7915d010e) from @OliverJAsh. +For "true" reusability you should also consider exposing a ref for your HOC. You can use `React.forwardRef` as documented in [the basic cheatsheet](https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/README.md#forwardrefcreateref), but we are interested in more real world examples. [Here is a nice example in practice](https://gist.github.com/OliverJAsh/d2f462b03b3e6c24f5588ca7915d010e) from @OliverJAsh (note - it still has some rough edges, we need help to test this out/document this).