Skip to content

Commit d0a05b2

Browse files
committed
Updated legacy patterns
1 parent 1259b21 commit d0a05b2

12 files changed

+104
-109
lines changed

README.md

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ import * as React from 'react';
176176
type Props = {
177177
label: string;
178178
count: number;
179-
onIncrement: () => any;
179+
onIncrement: () => void;
180180
};
181181

182182
export const FCCounter: React.FC<Props> = props => {
@@ -462,7 +462,7 @@ import { Subtract } from 'utility-types';
462462
// These props will be subtracted from base component props
463463
interface InjectedProps {
464464
count: number;
465-
onIncrement: () => any;
465+
onIncrement: () => void;
466466
}
467467

468468
export const withState = <BaseProps extends InjectedProps>(
@@ -534,7 +534,7 @@ const MISSING_ERROR = 'Error was swallowed during propagation.';
534534

535535
// These props will be subtracted from base component props
536536
interface InjectedProps {
537-
onReset: () => any;
537+
onReset: () => void;
538538
}
539539

540540
export const withErrorBoundary = <BaseProps extends InjectedProps>(
@@ -623,20 +623,6 @@ export default () => (
623623
624624
## Redux Connected Components
625625
626-
### Caveat with `bindActionCreators`
627-
**If you try to use `connect` or `bindActionCreators` explicitly and want to type your component callback props as `() => void` this will raise compiler errors. It happens because `bindActionCreators` typings will not map the return type of action creators to `void`, due to a current TypeScript limitations.**
628-
629-
A decent alternative I can recommend is to use `() => any` type, it will work just fine in all possible scenarios and should not cause any typing problems whatsoever. All the code examples in the Guide with `connect` are also using this pattern.
630-
631-
> If there is any progress or fix in regard to the above caveat I'll update the guide and make an announcement on my twitter/medium (There are a few existing proposals already).
632-
633-
> There is alternative way to retain type soundness but it requires an explicit wrapping with `dispatch` and will be very tedious for the long run. See example below:
634-
```ts
635-
const mapDispatchToProps = (dispatch: Dispatch<ActionType>) => ({
636-
onIncrement: () => dispatch(actions.increment()),
637-
});
638-
```
639-
640626
#### - redux connected counter
641627
642628
```tsx
@@ -650,11 +636,13 @@ const mapStateToProps = (state: Types.RootState) => ({
650636
count: countersSelectors.getReduxCounter(state.counters),
651637
});
652638

639+
const dispatchProps = {
640+
onIncrement: countersActions.increment,
641+
};
642+
653643
export const FCCounterConnected = connect(
654644
mapStateToProps,
655-
{
656-
onIncrement: countersActions.increment,
657-
}
645+
dispatchProps
658646
)(FCCounter);
659647

660648
```
@@ -735,11 +723,13 @@ const mapStateToProps = (state: Types.RootState, ownProps: OwnProps) => ({
735723
(ownProps.initialCount || 0),
736724
});
737725

726+
const dispatchProps = {
727+
onIncrement: countersActions.increment,
728+
};
729+
738730
export const FCCounterConnectedExtended = connect(
739731
mapStateToProps,
740-
{
741-
onIncrement: countersActions.increment,
742-
}
732+
dispatchProps
743733
)(FCCounter);
744734

745735
```
@@ -1131,12 +1121,28 @@ state.todos.push('Learn about tagged union types') // TS Error: Property 'push'
11311121
const newTodos = state.todos.concat('Learn about tagged union types') // OK
11321122
```
11331123
1134-
#### Caveat: Readonly is not recursive
1124+
#### Caveat - `Readonly` is not recursive
11351125
This means that the `readonly` modifier doesn't propagate immutability down the nested structure of objects. You'll need to mark each property on each level explicitly.
11361126
1137-
To fix this we can use [`DeepReadonly`](https://github.com/piotrwitek/utility-types#deepreadonlyt) type (available in `utility-types` npm library - collection of reusable types extending the collection of **standard-lib** in TypeScript.
1127+
> **TIP:** use `Readonly` or `ReadonlyArray` [Mapped types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
1128+
1129+
```ts
1130+
export type State = Readonly<{
1131+
counterPairs: ReadonlyArray<Readonly<{
1132+
immutableCounter1: number,
1133+
immutableCounter2: number,
1134+
}>>,
1135+
}>;
1136+
1137+
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
1138+
state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
1139+
state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
1140+
```
1141+
1142+
#### Solution - recursive `Readonly` is called `DeepReadonly`
1143+
1144+
To fix this we can use [`DeepReadonly`](https://github.com/piotrwitek/utility-types#deepreadonlyt) type (available from `utility-types`).
11381145
1139-
Check the example below:
11401146
```ts
11411147
import { DeepReadonly } from 'utility-types';
11421148

@@ -1152,21 +1158,6 @@ state.containerObject.innerValue = 1; // TS Error: cannot be mutated
11521158
state.containerObject.numbers.push(1); // TS Error: cannot use mutator methods
11531159
```
11541160
1155-
#### Best-practices for nested immutability
1156-
> use `Readonly` or `ReadonlyArray` [Mapped types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
1157-
1158-
```ts
1159-
export type State = Readonly<{
1160-
counterPairs: ReadonlyArray<Readonly<{
1161-
immutableCounter1: number,
1162-
immutableCounter2: number,
1163-
}>>,
1164-
}>;
1165-
1166-
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
1167-
state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
1168-
state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
1169-
```
11701161
11711162
[⇧ back to top](#table-of-contents)
11721163
@@ -1403,31 +1394,37 @@ export const getFilteredTodos = createSelector(getTodos, getTodosFilter, (todos,
14031394
14041395
## Typing connect
14051396
1406-
Below snippet can be find in the `playground/` folder, you can checkout the repo and follow all dependencies to understand the bigger picture.
1407-
`playground/src/connected/fc-counter-connected-verbose.tsx`
1397+
*__NOTE__: Below you'll find only a short explanation of concepts behind typing `connect`. For more advanced scenarios and common use-cases (`redux-thunk` and more...) please check [Redux Connected Components](#redux-connected-components) section.*
14081398
14091399
```tsx
1410-
import Types from 'Types';
1400+
import MyTypes from 'MyTypes';
14111401

14121402
import { bindActionCreators, Dispatch } from 'redux';
14131403
import { connect } from 'react-redux';
14141404

14151405
import { countersActions } from '../features/counters';
14161406
import { FCCounter } from '../components';
14171407

1418-
// `state` parameter needs a type annotation to type-check the correct shape of a state object but also it'll be used by "type inference" to infer the type of returned props
1419-
const mapStateToProps = (state: Types.RootState, ownProps: FCCounterProps) => ({
1408+
// `state` argument annotation is mandatory to check the correct shape of a state object and injected props
1409+
// you can also extend connected component Props type by annotating `ownProps` argument
1410+
const mapStateToProps = (state: MyTypes.RootState, ownProps: FCCounterProps) => ({
14201411
count: state.counters.reduxCounter,
14211412
});
14221413

1423-
// `dispatch` parameter needs a type annotation to type-check the correct shape of an action object when using dispatch function
1424-
const mapDispatchToProps = (dispatch: Dispatch<Types.RootAction>) => bindActionCreators({
1414+
// `dispatch` argument needs an annotation to check the correct shape of an action object
1415+
// when using dispatch function
1416+
const mapDispatchToProps = (dispatch: Dispatch<MyTypes.RootAction>) => bindActionCreators({
14251417
onIncrement: countersActions.increment,
1426-
// without using action creators, this will be validated using your RootAction union type
1427-
// onIncrement: () => dispatch({ type: "counters/INCREMENT" }),
14281418
}, dispatch);
14291419

1430-
// NOTE: We don't need to pass generic type arguments to neither connect nor mapping functions because type inference will do all this work automatically. So there's really no reason to increase the noise ratio in your codebase!
1420+
// shorter alternative is to use an object instead of mapDispatchToProps function
1421+
const dispatchToProps = {
1422+
onIncrement: countersActions.increment,
1423+
};
1424+
1425+
// Notice ee don't need to pass any generic type parameters to neither connect nor map functions above
1426+
// because type inference will infer types from arguments annotations automatically
1427+
// It's much cleaner and idiomatic approach
14311428
export const FCCounterConnectedVerbose =
14321429
connect(mapStateToProps, mapDispatchToProps)(FCCounter);
14331430
```
@@ -1622,6 +1619,7 @@ Object.values = () => [];
16221619
// "@src/*": ["src/*"] // will enable import aliases -> import { ... } from '@src/components'
16231620
// WARNING: Require to add this to your webpack config -> resolve: { alias: { '@src': PATH_TO_SRC } }
16241621
// "redux": ["typings/redux"], // override library types with your alternative type-definitions in typings folder
1622+
"redux-thunk": ["typings/redux-thunk"] // override library types with your alternative type-definitions in typings folder
16251623
},
16261624
"outDir": "dist/", // target for compiled files
16271625
"allowSyntheticDefaultImports": true, // no errors with commonjs modules interop

README_SOURCE.md

Lines changed: 39 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,6 @@ Adds error handling using componentDidCatch to any component
266266
267267
## Redux Connected Components
268268
269-
### Caveat with `bindActionCreators`
270-
**If you try to use `connect` or `bindActionCreators` explicitly and want to type your component callback props as `() => void` this will raise compiler errors. It happens because `bindActionCreators` typings will not map the return type of action creators to `void`, due to a current TypeScript limitations.**
271-
272-
A decent alternative I can recommend is to use `() => any` type, it will work just fine in all possible scenarios and should not cause any typing problems whatsoever. All the code examples in the Guide with `connect` are also using this pattern.
273-
274-
> If there is any progress or fix in regard to the above caveat I'll update the guide and make an announcement on my twitter/medium (There are a few existing proposals already).
275-
276-
> There is alternative way to retain type soundness but it requires an explicit wrapping with `dispatch` and will be very tedious for the long run. See example below:
277-
```ts
278-
const mapDispatchToProps = (dispatch: Dispatch<ActionType>) => ({
279-
onIncrement: () => dispatch(actions.increment()),
280-
});
281-
```
282-
283269
#### - redux connected counter
284270
285271
::codeblock='playground/src/connected/fc-counter-connected.tsx'::
@@ -289,15 +275,15 @@ const mapDispatchToProps = (dispatch: Dispatch<ActionType>) => ({
289275
290276
#### - redux connected counter (verbose)
291277
292-
::codeblock='playground/src/connected/fc-counter-connected-verbose.tsx'::
293-
::expander='playground/src/connected/fc-counter-connected-verbose.usage.tsx'::
278+
::codeblock='playground/src/connected/fc-counter-connected-bind-action-creators.tsx'::
279+
::expander='playground/src/connected/fc-counter-connected-bind-action-creators.usage.tsx'::
294280
295281
[⇧ back to top](#table-of-contents)
296282
297283
#### - with own props
298284
299-
::codeblock='playground/src/connected/fc-counter-connected-extended.tsx'::
300-
::expander='playground/src/connected/fc-counter-connected-extended.usage.tsx'::
285+
::codeblock='playground/src/connected/fc-counter-connected-own-props.tsx'::
286+
::expander='playground/src/connected/fc-counter-connected-own-props.usage.tsx'::
301287
302288
[⇧ back to top](#table-of-contents)
303289
@@ -425,12 +411,28 @@ state.todos.push('Learn about tagged union types') // TS Error: Property 'push'
425411
const newTodos = state.todos.concat('Learn about tagged union types') // OK
426412
```
427413
428-
#### Caveat: Readonly is not recursive
414+
#### Caveat - `Readonly` is not recursive
429415
This means that the `readonly` modifier doesn't propagate immutability down the nested structure of objects. You'll need to mark each property on each level explicitly.
430416
431-
To fix this we can use [`DeepReadonly`](https://github.com/piotrwitek/utility-types#deepreadonlyt) type (available in `utility-types` npm library - collection of reusable types extending the collection of **standard-lib** in TypeScript.
417+
> **TIP:** use `Readonly` or `ReadonlyArray` [Mapped types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
418+
419+
```ts
420+
export type State = Readonly<{
421+
counterPairs: ReadonlyArray<Readonly<{
422+
immutableCounter1: number,
423+
immutableCounter2: number,
424+
}>>,
425+
}>;
426+
427+
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
428+
state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
429+
state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
430+
```
431+
432+
#### Solution - recursive `Readonly` is called `DeepReadonly`
433+
434+
To fix this we can use [`DeepReadonly`](https://github.com/piotrwitek/utility-types#deepreadonlyt) type (available from `utility-types`).
432435
433-
Check the example below:
434436
```ts
435437
import { DeepReadonly } from 'utility-types';
436438

@@ -446,21 +448,6 @@ state.containerObject.innerValue = 1; // TS Error: cannot be mutated
446448
state.containerObject.numbers.push(1); // TS Error: cannot use mutator methods
447449
```
448450
449-
#### Best-practices for nested immutability
450-
> use `Readonly` or `ReadonlyArray` [Mapped types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
451-
452-
```ts
453-
export type State = Readonly<{
454-
counterPairs: ReadonlyArray<Readonly<{
455-
immutableCounter1: number,
456-
immutableCounter2: number,
457-
}>>,
458-
}>;
459-
460-
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
461-
state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
462-
state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
463-
```
464451
465452
[⇧ back to top](#table-of-contents)
466453
@@ -510,31 +497,37 @@ state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
510497
511498
## Typing connect
512499
513-
Below snippet can be find in the `playground/` folder, you can checkout the repo and follow all dependencies to understand the bigger picture.
514-
`playground/src/connected/fc-counter-connected-verbose.tsx`
500+
*__NOTE__: Below you'll find only a short explanation of concepts behind typing `connect`. For more advanced scenarios and common use-cases (`redux-thunk` and more...) please check [Redux Connected Components](#redux-connected-components) section.*
515501
516502
```tsx
517-
import Types from 'Types';
503+
import MyTypes from 'MyTypes';
518504

519505
import { bindActionCreators, Dispatch } from 'redux';
520506
import { connect } from 'react-redux';
521507

522508
import { countersActions } from '../features/counters';
523509
import { FCCounter } from '../components';
524510

525-
// `state` parameter needs a type annotation to type-check the correct shape of a state object but also it'll be used by "type inference" to infer the type of returned props
526-
const mapStateToProps = (state: Types.RootState, ownProps: FCCounterProps) => ({
511+
// `state` argument annotation is mandatory to check the correct shape of a state object and injected props
512+
// you can also extend connected component Props type by annotating `ownProps` argument
513+
const mapStateToProps = (state: MyTypes.RootState, ownProps: FCCounterProps) => ({
527514
count: state.counters.reduxCounter,
528515
});
529516

530-
// `dispatch` parameter needs a type annotation to type-check the correct shape of an action object when using dispatch function
531-
const mapDispatchToProps = (dispatch: Dispatch<Types.RootAction>) => bindActionCreators({
517+
// `dispatch` argument needs an annotation to check the correct shape of an action object
518+
// when using dispatch function
519+
const mapDispatchToProps = (dispatch: Dispatch<MyTypes.RootAction>) => bindActionCreators({
532520
onIncrement: countersActions.increment,
533-
// without using action creators, this will be validated using your RootAction union type
534-
// onIncrement: () => dispatch({ type: "counters/INCREMENT" }),
535521
}, dispatch);
536522

537-
// NOTE: We don't need to pass generic type arguments to neither connect nor mapping functions because type inference will do all this work automatically. So there's really no reason to increase the noise ratio in your codebase!
523+
// shorter alternative is to use an object instead of mapDispatchToProps function
524+
const dispatchToProps = {
525+
onIncrement: countersActions.increment,
526+
};
527+
528+
// Notice ee don't need to pass any generic type parameters to neither connect nor map functions above
529+
// because type inference will infer types from arguments annotations automatically
530+
// It's much cleaner and idiomatic approach
538531
export const FCCounterConnectedVerbose =
539532
connect(mapStateToProps, mapDispatchToProps)(FCCounter);
540533
```

playground/src/components/fc-counter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33
type Props = {
44
label: string;
55
count: number;
6-
onIncrement: () => any;
6+
onIncrement: () => void;
77
};
88

99
export const FCCounter: React.FC<Props> = props => {

playground/src/connected/fc-counter-connected-extended.spec.tsx renamed to playground/src/connected/fc-counter-connected-own-props.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createStore, combineReducers } from 'redux';
33
import { Provider } from 'react-redux';
44
import { render, fireEvent, cleanup } from 'react-testing-library';
55

6-
import { FCCounterConnectedExtended as ConnectedCounter } from './fc-counter-connected-extended';
6+
import { FCCounterConnectedExtended as ConnectedCounter } from './fc-counter-connected-own-props';
77

88
const reducer = combineReducers({
99
counters: combineReducers({

playground/src/connected/fc-counter-connected-extended.tsx renamed to playground/src/connected/fc-counter-connected-own-props.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ const mapStateToProps = (state: Types.RootState, ownProps: OwnProps) => ({
1414
(ownProps.initialCount || 0),
1515
});
1616

17+
const dispatchProps = {
18+
onIncrement: countersActions.increment,
19+
};
20+
1721
export const FCCounterConnectedExtended = connect(
1822
mapStateToProps,
19-
{
20-
onIncrement: countersActions.increment,
21-
}
23+
dispatchProps
2224
)(FCCounter);

playground/src/connected/fc-counter-connected.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ const mapStateToProps = (state: Types.RootState) => ({
88
count: countersSelectors.getReduxCounter(state.counters),
99
});
1010

11+
const dispatchProps = {
12+
onIncrement: countersActions.increment,
13+
};
14+
1115
export const FCCounterConnected = connect(
1216
mapStateToProps,
13-
{
14-
onIncrement: countersActions.increment,
15-
}
17+
dispatchProps
1618
)(FCCounter);

playground/src/connected/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export * from './fc-counter-connected-extended';
2-
export * from './fc-counter-connected-verbose';
1+
export * from './fc-counter-connected-bind-action-creators';
2+
export * from './fc-counter-connected-own-props';
33
export * from './fc-counter-connected';

playground/src/hoc/with-error-boundary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const MISSING_ERROR = 'Error was swallowed during propagation.';
55

66
// These props will be subtracted from base component props
77
interface InjectedProps {
8-
onReset: () => any;
8+
onReset: () => void;
99
}
1010

1111
export const withErrorBoundary = <BaseProps extends InjectedProps>(

0 commit comments

Comments
 (0)