Skip to content

Commit 3d9c771

Browse files
authored
Merge pull request piotrwitek#76 from piotrwitek/next
Next
2 parents 36ade6e + cb3469f commit 3d9c771

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+14711
-8850
lines changed

README.md

Lines changed: 239 additions & 303 deletions
Large diffs are not rendered by default.

docs/markdown/2_redux.md

Lines changed: 47 additions & 110 deletions
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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
# Recipes
22

33
### tsconfig.json
4-
- Recommended setup for best benefits from type-checking, with support for JSX and ES2016 features
5-
- Add [`tslib`](https://www.npmjs.com/package/tslib) to minimize bundle size: `npm i tslib` - this will externalize helper functions generated by transpiler and otherwise inlined in your modules
6-
- Include absolute imports config working with Webpack
4+
- 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 runtime helpers instead of adding them inline: `npm i tslib`
6+
- Example "paths" setup for baseUrl relative imports with Webpack
77

88
```js
99
{
1010
"compilerOptions": {
11-
"baseUrl": "./", // enables absolute path imports
12-
"paths": { // define absolute path mappings
11+
"baseUrl": "./", // enables project relative paths config
12+
"paths": { // define paths mappings
1313
"@src/*": ["src/*"] // will enable -> import { ... } from '@src/components'
1414
// in webpack you need to add -> resolve: { alias: { '@src': PATH_TO_SRC } }
1515
},
1616
"outDir": "dist/", // target for compiled files
17-
"allowSyntheticDefaultImports": true, // no errors on commonjs default import
17+
"allowSyntheticDefaultImports": true, // no errors with commonjs modules interop
18+
"esModuleInterop": true,
1819
"allowJs": true, // include js files
1920
"checkJs": true, // typecheck js files
2021
"declaration": false, // don't emit declarations
@@ -115,7 +116,7 @@ declare module 'rxjs/Subject' {
115116
116117
#### 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)
117118
118-
::example='../../playground/src/types/modules.d.ts'::
119+
::example='../../playground/typings/modules.d.ts'::
119120
120121
> 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)
121122

docs/markdown/7_links.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
# Project Examples
2-
3-
https://github.com/piotrwitek/react-redux-typescript-webpack-starter
4-
5-
[⇧ back to top](#table-of-contents)
6-
71
# Tutorials
82
> Curated list of relevant in-depth tutorials
93

docs/markdown/_intro.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
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).
5+
[![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)
66

7-
(_Compatible with **TypeScript v2.7.2**_)
7+
> #### _Found it usefull? Want more updates?_ [**Show your support by giving a :star:**](https://github.com/piotrwitek/react-redux-typescript-patterns/stargazers)
88
9-
[![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)
9+
> _[The Mighty Tutorial](https://github.com/piotrwitek/typesafe-actions#behold-the-mighty-tutorial) for completely typesafe Redux Architecture_ :book:
1010
11-
> #### _Found it usefull? Want more updates?_ [**Give it a :star2:**](https://github.com/piotrwitek/react-redux-typescript-patterns/stargazers)
11+
> _Reference implementation of Todo-App with `typesafe-actions`: https://codesandbox.io/s/github/piotrwitek/typesafe-actions-todo-app_ :computer:
12+
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)
1517
- Make type annotations concise by eliminating redudancy in types using advanced TypeScript Language features like **Type Inference** and **Control flow analysis**
1618
- Reduce repetition and complexity of types with TypeScript focused [complementary libraries](#complementary-libraries)
1719

20+
### Complementary Projects
21+
- Typesafe Action Creators for Redux / Flux Architectures [typesafe-actions](https://github.com/piotrwitek/typesafe-actions)
22+
- 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)
24+
1825
### Playground Project
1926
[![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)
2027

docs/markdown/_toc.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,4 @@
2929
- [Default and Named Module Exports](#default-and-named-module-exports)
3030
- [FAQ](#faq)
3131
- [Contribution Guide](#contribution-guide)
32-
- [Project Examples](#project-examples)
3332
- [Tutorials](#tutorials)

playground/.nvmrc

Lines changed: 0 additions & 1 deletion
This file was deleted.

playground/.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 80,
3+
"semi": true,
4+
"singleQuote": true,
5+
"trailingComma": "es5"
6+
}

playground/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
This folder is a playground for testing examples from `react-redux-typescript-guide`.
22

33
I have a secondary goal to create a separate project starter that could be used to bootstrap a real world application like this one: https://github.com/gothinkster/realworld
4+
5+
Standard Approach
6+
Opinionated Approach

0 commit comments

Comments
 (0)