Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

address old issues in cheatsheet #275

Merged
merged 4 commits into from
Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

<!--END-SECTION:learn-ts-->
Expand Down
18 changes: 18 additions & 0 deletions docs/advanced/patterns_by_usecase.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,24 @@ type NumbersChildren = number[];
type TwoNumbersChildren = [number, number];
```

<details>
<summary>
Don't forget that you can also use `prop-types` if TS fails you.
</summary>

```ts
Parent.propTypes = {
children: PropTypes.shape({
props: PropTypes.shape({
// could share `propTypes` to the child
value: PropTypes.string.isRequired,
}),
}).isRequired,
};
```

</details>

### 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 `<Routes>` can only have `<Route>` as children, nothing else is allowed" in TypeScript.
Expand Down
4 changes: 2 additions & 2 deletions docs/basic/getting-started/default-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const Greet = ({ age = 21 }: GreetProps) => {
// class components
// ////////////////
type GreetProps = {
age: number;
age?: number;
};

class Greet extends React.Component<GreetProps> {
Expand Down Expand Up @@ -125,7 +125,7 @@ export class MyComponent extends React.Component<IMyComponentProps> {

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).

</details>

Expand Down
140 changes: 78 additions & 62 deletions docs/basic/getting-started/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -24,37 +24,53 @@ const [user, setUser] = React.useState<IUser | null>(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<HTMLElement>(null!);
const ref2 = useRef<HTMLElement | null>(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)

<details>
<summary>What is the <code>!</code> at the end of <code>null!</code>?</summary>

`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<HTMLElement>(null!)` it means that you're instantiating the ref with a current value of `null` but lying to TypeScript that it's not `null`.
<summary><b>Usage with `Reducer` from `redux`</b></summary>

```ts
function MyComponent() {
const ref1 = useRef<HTMLElement>(null!);
useEffect(() => {
doSomethingWith(ref1.current); // TypeScript won't require null-check e.g. ref1 && ref1.current
});
return <div ref={ref1}> etc </div>;
}
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<State, Action>` which takes care of the return type for you.

So the above reducer example becomes:

```tsx
import { Reducer } from 'redux';

export function reducer: Reducer<AppState, Action>() {}
```

</details>

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:

Expand All @@ -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<HTMLElement>(null!);
const ref2 = useRef<HTMLElement | null>(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).

<details>
<summary>What is the <code>!</code> at the end of <code>null!</code>?</summary>

`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<HTMLElement>(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<HTMLElement>(null!);
useEffect(() => {
doSomethingWith(ref1.current); // TypeScript won't require null-check e.g. ref1 && ref1.current
});
return <div ref={ref1}> etc </div>;
}
```

</details>

The second option will make `ref2.current` mutable, and is intended for "instance variables" that you manage yourself.

```tsx
function TextInputWithFocusButton() {
Expand Down Expand Up @@ -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<ItemType> = {
items: ItemType[];
innerRef?: React.Ref<{ scrollToItem(item: ItemType): void }>;
};

function List<ItemType>(props: ListProps<ItemType>) {
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)

<details>

<summary><b>Usage with `Reducer` from `redux`</b></summary>

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<State, Action>` which takes care of the return type for you.

So the above reducer example becomes:

```tsx
import { Reducer } from 'redux';

export function reducer: Reducer<AppState, Action>() {}
```

</details>

**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):

Expand Down
2 changes: 1 addition & 1 deletion docs/hoc/full-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ export function inject<TProps, TInjectedKeys extends keyof TProps>(

### Using `forwardRef`

For "true" reusability you should also consider exposing a ref for your HOC. You can use `React.forwardRef<Ref, Props>` 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<Ref, Props>` 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).