Skip to content

Commit cb3469f

Browse files
committed
updated redux guide section to use newest tools and patterns cleaned and optimized for TS 2.8
1 parent 0a3946b commit cb3469f

26 files changed

+298
-446
lines changed

README.md

+154-207
Large diffs are not rendered by default.

docs/markdown/2_redux.md

+47-110
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
## Action Creators
44

5-
> Using Typesafe Action Creators helpers for Redux [`typesafe-actions`](https://github.com/piotrwitek/typesafe-actions#typesafe-actions)
5+
> We'll be using a battle-tested library [![NPM Downloads](https://img.shields.io/npm/dm/typesafe-actions.svg)](https://www.npmjs.com/package/typesafe-actions)
6+
that automates and simplify maintenace of **type annotations in Redux Architectures** [`typesafe-actions`](https://github.com/piotrwitek/typesafe-actions#typesafe-actions)
67

7-
A recommended approach is to use a simple functional helper to automate the creation of type-safe action creators. The advantage is that we can reduce a lot of code repetition and also minimize surface of errors by using type-checked API.
8-
> There are more specialized functional helpers available that will help you to further reduce tedious boilerplate and type-annotations in common scenarios like reducers (using [`getType`](https://github.com/piotrwitek/typesafe-actions#gettype)) or epics (using [`isActionOf`](https://github.com/piotrwitek/typesafe-actions#isactionof)).
9-
All that without losing type-safety! Please check this very short [Tutorial](https://github.com/piotrwitek/typesafe-actions#tutorial)
8+
### You should read [The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial) to learn it all the easy way!
109

11-
::example='../../playground/src/redux/counters/actions.ts'::
12-
::usage='../../playground/src/redux/counters/actions.usage.ts'::
10+
A solution below is using simple factory function to automate the creation of type-safe action creators. The goal is to reduce the maintainability and code repetition of type annotations for actions and creators and the result is completely typesafe action-creators and their actions.
11+
12+
::example='../../playground/src/features/counters/actions.ts'::
13+
::usage='../../playground/src/features/counters/actions.usage.ts'::
1314

1415
[⇧ back to top](#table-of-contents)
1516

@@ -18,10 +19,11 @@ All that without losing type-safety! Please check this very short [Tutorial](htt
1819
## Reducers
1920

2021
### State with Type-level Immutability
21-
Declare reducer `State` type with `readonly` modifier to get "type level" immutability
22+
Declare reducer `State` type with `readonly` modifier to get compile time immutability
2223
```ts
2324
export type State = {
24-
readonly counter: number,
25+
readonly counter: number;
26+
readonly todos: ReadonlyArray<string>;
2527
};
2628
```
2729

@@ -31,25 +33,34 @@ export const initialState: State = {
3133
counter: 0,
3234
}; // OK
3335

34-
initialState.counter = 3; // Error, cannot be mutated
36+
initialState.counter = 3; // TS Error: cannot be mutated
37+
```
38+
39+
It's great for **Arrays in JS** because it will error when using mutator methods like (`push`, `pop`, `splice`, ...), but it'll still allow immutable methods like (`concat`, `map`, `slice`,...).
40+
```ts
41+
state.todos.push('Learn about tagged union types') // TS Error: Property 'push' does not exist on type 'ReadonlyArray<string>'
42+
const newTodos = state.todos.concat('Learn about tagged union types') // OK
3543
```
3644

37-
#### Caveat: Readonly does not provide a recursive immutability on objects
38-
This means that the `readonly` modifier doesn't propagate immutability down to "properties" of objects. You'll need to set it explicitly on each nested property that you want.
45+
#### Caveat: Readonly is not recursive
46+
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.
47+
48+
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.
3949

4050
Check the example below:
4151
```ts
42-
export type State = {
43-
readonly containerObject: {
44-
readonly immutableProp: number,
45-
mutableProp: number,
46-
}
47-
};
52+
import { DeepReadonly } from 'utility-types';
4853

49-
state.containerObject = { mutableProp: 1 }; // Error, cannot be mutated
50-
state.containerObject.immutableProp = 1; // Error, cannot be mutated
54+
export type State = DeepReadonly<{
55+
containerObject: {
56+
innerValue: number,
57+
numbers: number[],
58+
}
59+
}>;
5160

52-
state.containerObject.mutableProp = 1; // OK! No error, can be mutated
61+
state.containerObject = { innerValue: 1 }; // TS Error: cannot be mutated
62+
state.containerObject.innerValue = 1; // TS Error: cannot be mutated
63+
state.containerObject.numbers.push(1); // TS Error: cannot use mutator methods
5364
```
5465

5566
#### Best-practices for nested immutability
@@ -63,65 +74,58 @@ export type State = Readonly<{
6374
}>>,
6475
}>;
6576

66-
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // Error, cannot be mutated
67-
state.counterPairs[0].immutableCounter1 = 1; // Error, cannot be mutated
68-
state.counterPairs[0].immutableCounter2 = 1; // Error, cannot be mutated
77+
state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
78+
state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
79+
state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
6980
```
7081

71-
> _There is a new (work in progress) feature called **Conditional Types**, that will allow `ReadonlyRecursive` mapped type_
72-
7382
[⇧ back to top](#table-of-contents)
7483

7584
### Typing reducer
76-
> using type inference with [Discriminated Union types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
85+
> to understand following section make sure to learn about [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html), [Control flow analysis](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#control-flow-based-type-analysis) and [Tagged union types](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types)
7786
78-
::example='../../playground/src/redux/todos/reducer.ts'::
87+
::example='../../playground/src/features/todos/reducer.ts'::
7988

8089
[⇧ back to top](#table-of-contents)
8190

8291
### Testing reducer
8392

84-
::example='../../playground/src/redux/todos/reducer.spec.ts'::
93+
::example='../../playground/src/features/todos/reducer.spec.ts'::
8594

8695
[⇧ back to top](#table-of-contents)
8796

88-
8997
---
9098

9199
## Store Configuration
92100

93-
### Create Root State and Root Action Types
101+
### Create Global RootState and RootAction Types
94102

95-
#### `RootState` - interface representing redux state tree
103+
#### `RootState` - type representing root state-tree
96104
Can be imported in connected components to provide type-safety to Redux `connect` function
97105

98-
::example='../../playground/src/redux/root-reducer.ts'::
99-
100-
[⇧ back to top](#table-of-contents)
101-
102-
#### `RootAction` - union type of all action objects
106+
#### `RootAction` - type representing union type of all action objects
103107
Can be imported in various layers receiving or sending redux actions like: reducers, sagas or redux-observables epics
104108

105-
::example='../../playground/src/redux/root-action.ts'::
109+
::example='../../playground/src/store/types.d.ts'::
106110

107111
[⇧ back to top](#table-of-contents)
108112

109113
### Create Store
110114

111-
When creating the store, use rootReducer. This will set-up a **strongly typed Store instance** with type inference.
112-
> The resulting store instance methods like `getState` or `dispatch` will be type checked and expose type errors
115+
When creating a store instance we don't need to provide any additional types. It will set-up a **type-safe Store instance** using type inference.
116+
> The resulting store instance methods like `getState` or `dispatch` will be type checked and will expose all type errors
113117
114-
::example='../../playground/src/store.ts'::
118+
::example='../../playground/src/store/store.ts'::
115119

116120
---
117121

118122
## Async Flow
119123

120124
### "redux-observable"
121125

122-
Use `isActionOf` helper to filter actions and to narrow `RootAction` union type to a specific "action type" down the stream.
126+
### For more examples and in-depth explanation you should read [The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial) to learn it all the easy way!
123127

124-
::example='../../playground/src/redux/toasts/epics.ts'::
128+
::example='../../playground/src/features/todos/epics.ts'::
125129

126130
[⇧ back to top](#table-of-contents)
127131

@@ -131,73 +135,6 @@ Use `isActionOf` helper to filter actions and to narrow `RootAction` union type
131135

132136
### "reselect"
133137

134-
```ts
135-
import { createSelector } from 'reselect';
136-
137-
import { RootState } from '@src/redux';
138-
139-
export const getTodos =
140-
(state: RootState) => state.todos.todos;
141-
142-
export const getTodosFilter =
143-
(state: RootState) => state.todos.todosFilter;
144-
145-
export const getFilteredTodos = createSelector(
146-
getTodos, getTodosFilter,
147-
(todos, todosFilter) => {
148-
switch (todosFilter) {
149-
case 'completed':
150-
return todos.filter((t) => t.completed);
151-
case 'active':
152-
return todos.filter((t) => !t.completed);
153-
154-
default:
155-
return todos;
156-
}
157-
},
158-
);
159-
```
160-
161-
[⇧ back to top](#table-of-contents)
162-
163-
---
164-
165-
### Action Creators - Alternative Pattern
166-
This pattern is focused on a KISS principle - to stay clear of abstractions and to follow a more complex but familiar JavaScript "const" based approach:
167-
168-
Advantages:
169-
- familiar to standard JS "const" based approach
170-
171-
Disadvantages:
172-
- significant amount of boilerplate and duplication
173-
- more complex compared to `createAction` helper library
174-
- necessary to export both action types and action creators to re-use in other places, e.g. `redux-saga` or `redux-observable`
175-
176-
```tsx
177-
export const INCREMENT = 'INCREMENT';
178-
export const ADD = 'ADD';
179-
180-
export type Actions = {
181-
INCREMENT: {
182-
type: typeof INCREMENT,
183-
},
184-
ADD: {
185-
type: typeof ADD,
186-
payload: number,
187-
},
188-
};
189-
190-
export type RootAction = Actions[keyof Actions];
191-
192-
export const actions = {
193-
increment: (): Actions[typeof INCREMENT] => ({
194-
type: INCREMENT,
195-
}),
196-
add: (amount: number): Actions[typeof ADD] => ({
197-
type: ADD,
198-
payload: amount,
199-
}),
200-
};
201-
```
138+
::example='../../playground/src/features/todos/selectors.ts'::
202139

203140
[⇧ back to top](#table-of-contents)

docs/markdown/4_recipes.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
### tsconfig.json
44
- Recommended baseline config carefully optimized for strict type-checking and optimal webpack workflow
5-
- Install [`tslib`](https://www.npmjs.com/package/tslib) to cut on bundle size, by using external transpiltion helper module instead of adding them inline: `npm i tslib`
6-
- Example setup for project relative path imports with Webpack
5+
- Install [`tslib`](https://www.npmjs.com/package/tslib) to cut on bundle size, by using external runtime helpers instead of adding them inline: `npm i tslib`
6+
- Example "paths" setup for baseUrl relative imports with Webpack
77

88
```js
99
{
@@ -116,7 +116,7 @@ declare module 'rxjs/Subject' {
116116
117117
#### To quick-fix missing type declarations for vendor modules you can "assert" a module type with `any` using [Shorthand Ambient Modules](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Modules.md#shorthand-ambient-modules)
118118
119-
::example='../../playground/src/types/modules.d.ts'::
119+
::example='../../playground/typings/modules.d.ts'::
120120
121121
> More advanced scenarios for working with vendor module declarations can be found here [Official TypeScript Docs](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Modules.md#working-with-other-javascript-libraries)
122122

docs/markdown/_intro.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
# React & Redux in TypeScript - Static Typing Guide
1+
## React & Redux in TypeScript - Static Typing Guide
22

33
_"This guide is a **living compendium** documenting the most important patterns and recipes on how to use **React** (and it's Ecosystem) in a **functional style with TypeScript** and to make your code **completely type-safe** while focusing on a **conciseness of type annotations** so it's a minimal effort to write and to maintain types in the long run."_
44

5-
To provide the best experience we focus on the symbiosis of type-safe complementary libraries and learning the concepts like [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html), [Control flow analysis](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#control-flow-based-type-analysis), [Generics](https://www.typescriptlang.org/docs/handbook/generics.html) and some [Advanced Types](https://www.typescriptlang.org/docs/handbook/advanced-types.html).
6-
75
[![Join the chat at https://gitter.im/react-redux-typescript-guide/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/react-redux-typescript-guide/Lobby)
86

9-
> _Now compatible with **TypeScript v2.8.3**_
7+
> #### _Found it usefull? Want more updates?_ [**Show your support by giving a :star:**](https://github.com/piotrwitek/react-redux-typescript-patterns/stargazers)
8+
9+
> _[The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial) for completely typesafe Redux Architecture_ :book:
10+
11+
> _Reference implementation of Todo-App with `typesafe-actions`: https://codesandbox.io/s/github/piotrwitek/typesafe-actions-todo-app_ :computer:
1012
11-
> #### _Found it usefull? Want more updates?_ [**Give it a :star2:**](https://github.com/piotrwitek/react-redux-typescript-patterns/stargazers)
13+
> _Now compatible with **TypeScript v2.8.3** (rewritten using conditional types)_ :tada:
1214
1315
### Goals
1416
- Complete type safety (with [`--strict`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) flag) without loosing type information downstream through all the layers of our application (e.g. no type assertions or hacking with `any` type)
@@ -17,8 +19,8 @@ To provide the best experience we focus on the symbiosis of type-safe complement
1719

1820
### Complementary Projects
1921
- Typesafe Action Creators for Redux / Flux Architectures [typesafe-actions](https://github.com/piotrwitek/typesafe-actions)
20-
- Reference implementation of Todo-App: [typesafe-actions-todo-app](https://github.com/piotrwitek/typesafe-actions-todo-app)
2122
- Utility Types for TypeScript: [utility-types](https://github.com/piotrwitek/utility-types)
23+
- Reference implementation of Todo-App: [typesafe-actions-todo-app](https://github.com/piotrwitek/typesafe-actions-todo-app)
2224

2325
### Playground Project
2426
[![Codeship Status for piotrwitek/react-redux-typescript-guide](https://app.codeship.com/projects/11eb8c10-d117-0135-6c51-26e28af241d2/status?branch=master)](https://app.codeship.com/projects/262359)

playground/package-lock.json

+7-31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"rxjs": "6.1.0",
3737
"rxjs-compat": "6.1.0",
3838
"tslib": "1.9.1",
39-
"typesafe-actions": "2.0.0-rc.1",
39+
"typesafe-actions": "2.0.3",
4040
"utility-types": "2.0.0"
4141
},
4242
"devDependencies": {
@@ -45,7 +45,7 @@
4545
"@types/prop-types": "15.5.2",
4646
"@types/react": "16.3.14",
4747
"@types/react-dom": "16.0.5",
48-
"@types/react-redux": "5.0.16",
48+
"@types/react-redux": "6.0.0",
4949
"@types/react-router-dom": "4.2.6",
5050
"@types/react-router-redux": "5.0.14",
5151
"enzyme": "3.3.0",

playground/src/features/counters/actions.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import { action, createAction, createStandardAction } from 'typesafe-actions';
22

33
import { ADD, INCREMENT } from './constants';
44

5-
// SIMPLE TYPESAFE-ACTION
5+
// CLASSIC API
66
export const increment = () => action(INCREMENT);
77
export const add = (amount: number) => action(ADD, amount);
88

9-
// EQUIVALENT ALTERNATIVES that will allow to use "action-creators" instead of "constants" in reducers & epics:
10-
// check more info here: https://github.com/piotrwitek/typesafe-actions#tutorial
11-
// OPTION 1:
9+
// ALTERNATIVE API - allow to use reference to "action-creator" function instead of "type constant"
10+
// e.g. case getType(increment): return { ... }
11+
// This will allow to completely eliminate need for "constants" in your application, more info here:
12+
// https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial
13+
14+
// OPTION 1 (with generics):
1215
// export const increment = createStandardAction(INCREMENT)<void>();
1316
// export const add = createStandardAction(ADD)<number>();
1417

15-
// OPTION 2:
18+
// OPTION 2 (with resolve callback):
1619
// export const increment = createAction(INCREMENT);
1720
// export const add = createAction(ADD, resolve => {
1821
// return (amount: number) => resolve(amount);

0 commit comments

Comments
 (0)