Skip to content

Commit 65c3d6c

Browse files
committed
Updates docs
1 parent 078b2ae commit 65c3d6c

File tree

9 files changed

+280
-30
lines changed

9 files changed

+280
-30
lines changed

website/docs/.vuepress/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ module.exports = {
112112
'typescript-tutorial/typed-hooks',
113113
'typescript-tutorial/adding-typed-actions',
114114
'typescript-tutorial/adding-typed-thunks',
115+
'typescript-tutorial/using-typed-injections',
116+
'typescript-tutorial/typing-thunk-against-the-store',
117+
'typescript-tutorial/adding-typed-listeners',
115118
],
116119
},
117120
{

website/docs/docs/typescript-tutorial/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
This section will guide you through integrating and using Typescript with Easy Peasy. It will start with the basics, focusing on state only, and then introduce each of the APIs (e.g. an [action](/docs/api/action), [thunk](/docs/api/thunk), etc) as it proceeds.
44

5+
If you aren't familiar with Easy Peasy then I would recommend that you first familiarise yourself with it by reading the [tutorial](/docs/tutorial). This Typescript tutorial will not go into detail regarding the APIs.
6+
57
> **Note:** this section is a work in progress. I will be updating it as I get through it. Hopefully within a week it will be fairly complete. I appreciate your patience.

website/docs/docs/typescript-tutorial/adding-typed-actions.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
# Adding typed actions
22

3-
In order to declare an action Easy Peasy exports an `Action` type. The full typing definition for the `Action` type is:
3+
Easy Peasy exports an `Action` type, allowing you declare an [action](/docs/api/action) against your model interface. The definition for this type is:
44

55
```typescript
66
Action<Model extends Object = {}, Payload = void>
77
```
88

9-
As you can see it accepts 2 type parameters. They can be described as follows.
9+
As you can see it accepts 2 type parameters, with both being optional. These type parameters can be described as follows.
1010

1111
- `Model`
1212

13-
The model against which the action is being bound. This allows us to ensure the the `state` that is exposed to our action is correctly typed.
13+
The model against which the action is being bound. This allows us to ensure the the `state` that is exposed to our [action](/docs/api/action) implementation is correctly typed.
1414

1515
- `Payload`
1616

17-
If you expect the action to receive a payload then you should provide the type for the payload. If your action will not receive any payload you can omit this type parameter.
17+
If you expect the [action](/docs/api/action) implementation to receive a payload then you should provide the type for the payload. If your [action](/docs/api/action) will not receive any payload you can omit this type parameter.
1818

19-
Let's define an action that will allow us to add another todo.
19+
Let's define an [action](/docs/api/action) that will allow us to add a todo.
2020

2121
```typescript
2222
import { Action } from 'easy-peasy'; // 👈 import the type
@@ -27,9 +27,9 @@ export interface TodosModel {
2727
}
2828
```
2929

30-
As you can see our `Action` is operating against the `TodosModel` and it expects a payload of `string`.
30+
We have provided type parameter to our `Action` informing it that it is operating against the `TodosModel` and that it should expect a payload of type `string`.
3131

32-
We can now implement this action against our model.
32+
We can now implement this [action](/docs/api/action) against our model.
3333

3434
```typescript
3535
import { action } from 'easy-peasy';
@@ -42,11 +42,11 @@ const todos: TodosModel = {
4242
};
4343
```
4444

45-
You will have noted that Typescript was providing us with the typing information and assertions whilst we implemented our action.
45+
You will have noted that Typescript was providing us with the typing information and assertions whilst we implemented our [action](/docs/api/action).
4646

4747
***TODO: Screenshot of typing information on action implementation***
4848

49-
We can now consume the action within our component, making sure we use the typed version of `useStoreActions` that we exported from our store.
49+
We can now consume the [action](/docs/api/action) within our components, whilst making sure that we use the typed `useStoreActions` that we exported from our [store](/docs/api/store).
5050

5151
```typescript
5252
import { useStoreActions } from './my-store'; // 👈 import typed hook
@@ -55,8 +55,6 @@ function AddTodo() {
5555
// map the addTodo action 👇
5656
const addTodo = useStoreActions(actions => actions.todos.addTodo);
5757

58-
// The below are the standard React hooks we are using to manage the form
59-
// state and the button onClick callback
6058
const [text, setText] = useState('');
6159
const onButtonClick = useCallback(() => {
6260
addTodo(text); // 👈 dispatch our action with the text describing the todo
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Adding typed listeners
2+
3+
In the previous section we extended our [thunk](/docs/api/thunk) implementation so that it would create an audit log entry every time a todo was saved. This isn't the best design as the todos model shouldn't have to know about the audit model.
4+
5+
An alternative, cleaner approach, would be for the audit model to respond to the `addTodo` action, logging accordingly. We can achieve this via an [action](/docs/api/action) configured as a listener. Let's refactor our implementation to do this.
6+
7+
Firstly, we will add a new [action](/docs/api/action) to the interface definition for our audit model. This [action](/docs/api/action) will be used as the listener.
8+
9+
```typescript
10+
interface AuditModel {
11+
log: string[];
12+
addLog: Action<AuditModel, string>;
13+
onTodoAdded: Action<AuditModel, string>; // 👈 new action which will be used
14+
// as a listener
15+
}
16+
```
17+
18+
Now we will implement the [action](/docs/api/action), configuring to to listen to the `addTodo` [action](/docs/api/action).
19+
20+
```typescript
21+
import todos from './todos-model';
22+
23+
const auditModel: AuditModel = {
24+
logs: [],
25+
addLog: action((state, payload) => {
26+
state.logs.push(payload)
27+
}),
28+
onTodoAdded: action(
29+
(state, payload) => {
30+
state.push(`Added todo: "${payload}"`);
31+
},
32+
{ listenTo: todos.addTodo } // 👈 binding the action to listen to.
33+
)
34+
};
35+
```
36+
37+
Now every time the `addTodo` [action](/docs/api/action) is fired our `onTodoAdded` listening [action](/docs/api/action) will fire and add an audit log.
38+
39+
Remember, our listening [action](/docs/api/action) will receive the same payload as the target [action](/docs/api/action). Therefore we need to ensure that the payload types will match across the listener and target. If they do not match a Typescript error will occur warning you of this fact.
40+
41+
***TODO: Screenshot of error for mistmatching payload***
42+
43+
We can now remove the call to the audit model within our `saveTodo` thunk.
44+
45+
```typescript
46+
const todosModel: TodosModel = {
47+
items: [],
48+
addTodo: action((state, payload) => {
49+
state.items.push(payload);
50+
}),
51+
saveTodo: thunk(async (actions, payload, { injections }) => {
52+
const { todosService } = injections;
53+
await todosService.save(payload);
54+
actions.addTodo(payload);
55+
getStoreActions().audit.addLog(payload); // 👈 remove this line
56+
})
57+
};
58+
```

website/docs/docs/typescript-tutorial/adding-typed-thunks.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Adding typed thunks
22

3-
In order to declare a [thunk](/docs/api/thunk) Easy Peasy exports a `Thunk` type. The full typing definition for the `Thunk` type is:
3+
Easy Peasy exports a `Thunk` type, allowing you to declare a [thunk](/docs/api/thunk) against your model interface. The full typing definition for the `Thunk` type is:
44

55
```typescript
66
type Thunk<
@@ -12,11 +12,13 @@ type Thunk<
1212
>
1313
```
1414

15-
As you can see it accepts 5 type parameters. That may seem like a lot, but in most implementations you will likely only need to provide 2 of them. I've tried to order them so that they most frequently used type parameters are defined first. The type parameters can be described as follows.
15+
As you can see it accepts 5 type parameters, all of them optional. This may seem like a lot of type parameters, but in most cases you will likely only need to provide 2 of them. We have tried to order the type parameters from the most to the least frequently used, based on our experience with [thunks](/docs/api/thunk).
16+
17+
The type parameters can be described as follows.
1618

1719
- `Model`
1820

19-
The model against which the [thunk](/docs/api/thunk) is being bound. This allows us to ensure the the `actions` that are exposed to our [thunk](/docs/api/thunk) are correctly typed.
21+
The model against which the [thunk](/docs/api/thunk) is being bound. This allows us to ensure the the `actions` argument that is provided to our [thunks](/docs/api/thunk) are correctly typed.
2022

2123
- `Payload`
2224

@@ -41,5 +43,60 @@ As you can see it accepts 5 type parameters. That may seem like a lot, but in mo
4143
Let's define a thunk that will allow us to save a todo by posting to an HTTP endpoint.
4244

4345
```typescript
44-
TODO
45-
```
46+
import { Thunk } from 'easy-peasy';
47+
48+
export interface TodosModel {
49+
items: string[];
50+
addTodo: Action<TodosModel, string>;
51+
saveTodo: Thunk<TodosModel, string>; // 👈 declaring our thunk
52+
}
53+
```
54+
55+
As you can see our `Thunk` is operating against the `TodosModel` and it expects a payload of `string`.
56+
57+
We can now implement this action against our model.
58+
59+
```typescript
60+
import { thunk } from 'easy-peasy';
61+
62+
const todosModel: TodosModel = {
63+
items: [],
64+
addTodo: action((state, payload) => {
65+
state.items.push(payload);
66+
}),
67+
saveTodo: thunk(async (actions, payload) => {
68+
await todosService.save(payload); // imagine calling an HTTP service
69+
actions.addTodo(payload);
70+
})
71+
};
72+
```
73+
74+
You will have noted that Typescript was providing us with the typing information and assertions whilst we implemented our [thunk](/docs/api/thunk).
75+
76+
***TODO: Screenshot of typing information on thunk implementation***
77+
78+
We can now consume the [thunk](/docs/api/thunk) within our component, making sure we use the typed version of `useStoreActions` that we exported from our store. We will refactor our component from earlier.
79+
80+
```typescript
81+
import { useStoreActions } from './my-store'; // 👈 import typed hook
82+
83+
function AddTodo() {
84+
// map the saveTodo thunk 👇
85+
const saveTodo = useStoreActions(actions => actions.todos.saveTodo);
86+
87+
const [text, setText] = useState('');
88+
const onButtonClick = useCallback(() => {
89+
saveTodo(text) // 👈 dispatch our thunk with the text describing the todo
90+
.then(() => setText('')); // then chain off the promise returned by the thunk
91+
}, [addTodo, setText, text]);
92+
93+
return (
94+
<>
95+
<input text={text} onChange={e => setText(e.target.value)} type="text />
96+
<button onClick={onButtonClick}>Add Todo</button>
97+
</>
98+
);
99+
}
100+
```
101+
102+
***TODO: Screenshot of typing information on thunk dispatch***

website/docs/docs/typescript-tutorial/create-your-store.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Create your store
22

3-
The heart of the Typescript integration with Easy Peasy are the typing definitions you provide for your model. From this Easy Peasy will be able to provide you with typing information, code completion, and assertions across the rest of its APIs.
3+
The heart of the Typescript integration with Easy Peasy are the typing definitions you define to represent your [store's](/docs/api/store) model. This typing information can then be used by the various Easy Peasy APIs to provide you with assertions, code completions, etc.
44

5-
Our model will consist of two slices; todos and an audit log. For now we will only define the state within our model, no actions etc.
5+
For this tutorial we will create a model consisting of two slices; todos and an audit log.
6+
7+
We will start by defining the state for our model - without actions/thunks/etc.
68

79
```typescript
810
// The interface representing our Todos model
@@ -22,18 +24,18 @@ interface StoreModel {
2224
}
2325
```
2426

25-
Using our model interface we can create our initial model implementation.
27+
Using our model interfaces we can create our model implementation.
2628

2729
```typescript
28-
const todos: TodosModel = {
30+
const todosModel: TodosModel = {
2931
items: []
3032
};
3133

32-
const audit: AuditModel = {
34+
const auditModel: AuditModel = {
3335
logs: []
3436
};
3537

36-
const model: StoreModel = {
38+
const storeModel: StoreModel = {
3739
todos,
3840
audit
3941
}
@@ -43,7 +45,7 @@ Good ol' Typescript will make sure that we implement the model in full.
4345

4446
***TODO: Screenshot of validation against model***
4547

46-
You can organise your interfaces and model implementation as you please. My personal preference is to split them out by slice, for example.
48+
You can organise your model interfaces and implementations as you please. My personal preference is to split them out into seperate files based on slice/feature.
4749

4850
```typescript
4951
// todos.ts
@@ -52,17 +54,19 @@ export interface TodosModel {
5254
items: Todo[];
5355
}
5456

55-
const todos: TodosModel = {
57+
const todosModel: TodosModel = {
5658
items: []
5759
};
5860

59-
export default todos;
61+
export default todosModel;
6062
```
6163

6264
Now that we have our model defined we can pass it to [createStore](/docs/api/create-store) in order to create our [store](/docs/api/store).
6365

6466
```typescript
65-
const store = createStore(model);
67+
import storeModel from './model';
68+
69+
const store = createStore(storeModel);
6670
```
6771

6872
The [store](/docs/api/store) that is returned will be fully typed. If you try to use the [store's](/docs/api/store) APIs you will note the typing information and code completion being offered by your IDE.

website/docs/docs/typescript-tutorial/typed-hooks.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
# Using typed hooks
22

3-
For convenience we bind the hooks to your create [store](/docs/api/store) instances. This is especially useful in the context of Typescript because all the typing information of your model will be automatically baked into these hooks. We therefore recommend that you export these hooks of your store so that you can easily import and use them within your components.
3+
For convenience we bind the Easy Peasy's hooks against a [store](/docs/api/store) instance.
4+
5+
If you were to use the hooks imported directly from the Easy Peasy library, e.g. `import { useStoreActions } from 'easy-peasy';`, you would have to provide the model interface that represents your store every time you used them.
6+
7+
By binding the hooks against a [store](/docs/api/store) instance we provide a convenient mechanism for you to avoid have to do this.
8+
9+
We recommend that you extract these typed hooks off your [store](/docs/api/store) instance, and export them so that you can easily use them in your components.
410

511
```typescript
612
// my-store.ts
713

814
import { createStore } from 'easy-peasy';
9-
import model from './model';
15+
import storeModel from './model';
1016

11-
const store = createStore(model);
17+
const store = createStore(storeModel);
1218

1319
// 👇export the typed hooks
1420
export const useStoreActions = store.useStoreActions;
@@ -18,7 +24,7 @@ export const useStoreState = store.useStoreState;
1824
export default store;
1925
```
2026

21-
You could then import them into your components, and receive the benefit of all the model typing being available.
27+
We can now import the typed hooks into a component.
2228

2329
```typescript
2430
import { useStoreState } from './my-store'; // 👈 import the typed hook
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Exposing entire store to our thunk
2+
3+
We will now extend our [thunk](/docs/api/thunk) so that it is able to get the state/actions for the entire [store](/docs/api/store), after which we will update our [thunk](/docs/api/thunk) implementation so that it will create an audit log entry every time a todo is saved.
4+
5+
Firstly, let's ensure that our audit model has an [action](/docs/api/action) on it which allows us to create a new log entry.
6+
7+
```typescript
8+
interface AuditModel {
9+
log: string[];
10+
addLog: Action<AuditModel, string>;
11+
}
12+
13+
const audit: AuditModel = {
14+
logs: [],
15+
addLog: action((state, payload) => {
16+
state.logs.push(payload)
17+
})
18+
};
19+
```
20+
21+
Now let's update our [thunk](/docs/api/thunk) configuration so that it is aware of the typings that represent our entire [store](/docs/api/store).
22+
23+
```typescript
24+
import { StoreModel } from './model';
25+
// 👆 import the interface that represents our store model
26+
// don't worry about circular references, this is allowed
27+
28+
export interface TodosModel {
29+
items: string[];
30+
addTodo: Action<TodosModel, string>;
31+
saveTodo: Thunk<
32+
TodosModel,
33+
string,
34+
StoreInjections,
35+
StoreModel // 👈 provide the store model interface
36+
>;
37+
}
38+
```
39+
40+
We can now refactor our [thunk](/docs/api/thunk) implementation to make use of the `getStoreActions` helper that is provided to it.
41+
42+
```typescript
43+
const todosModel: TodosModel = {
44+
items: [],
45+
addTodo: action((state, payload) => {
46+
state.items.push(payload);
47+
}),
48+
saveTodo: thunk(async (actions, payload, { injections, getStoreActions }) => {
49+
const { todosService } = injections;
50+
await todosService.save(payload);
51+
actions.addTodo(payload);
52+
getStoreActions().audit.addLog(payload); // 👈 accessing global actions
53+
})
54+
};
55+
```
56+
57+
As you can see above we were able to get full type information against the `getStoreActions` helper, allowing us to add a log entry. It is important to note that the `getStoreState` helper would work equally as well.
58+
59+
```typescript
60+
thunk(async (actions, payload, { getStoreState }) => {
61+
console.log(getStoreState().audit.logs);
62+
})
63+
```

0 commit comments

Comments
 (0)