Skip to content

Commit bb7b3ad

Browse files
christianalfonigitbook-bot
authored andcommitted
GitBook: [master] 12 pages modified
1 parent 7314793 commit bb7b3ad

File tree

10 files changed

+259
-484
lines changed

10 files changed

+259
-484
lines changed

SUMMARY.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
## Core
1010

1111
* [State](core/defining-state.md)
12-
* [Actions](core/writing-application-logic.md)
13-
* [Effects](core/running-side-effects.md)
14-
* [Operators](features/going-functional.md)
12+
* [Actions](features/writing-application-logic.md)
13+
* [Effects](features/running-side-effects.md)
14+
* [Operators](core/going-functional.md)
1515
* [Statecharts](features/statecharts.md)
1616
* [Typescript](core/typescript.md)
1717

@@ -32,6 +32,7 @@
3232
* [Managing lists](guides-1/managing-lists.md)
3333
* [State first routing](guides-1/state-first-routing.md)
3434
* [Server Side Rendering](features/server-side-rendering.md)
35+
* [Move to Typescript](guides-1/move-to-typescript.md)
3536

3637
## API <a id="api-1"></a>
3738

core/defining-state.md

Lines changed: 134 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ You might wonder why **undefined** is not part of the core value types. Well, th
1919
1. It is not a serializable value. That means if you explicitly set a value to _undefined_ it will not show up in the devtools
2020
2. Undefined values can not be tracked. That means if you were to iterate an object and look at the keys of that object, any undefined values will not be tracked. This can cause unexpected behaviour
2121

22+
{% hint style="info" %}
23+
When writing Typescript you should **not** use optional values for your state \(**?**\), or use **undefined** in a union type. In a serializable state store world **null** is the value indicating _“there is no value”_.
24+
25+
```typescript
26+
type State = {
27+
// Do not do this
28+
foo?: string
29+
30+
// Do not do this
31+
foo: string | undefined
32+
33+
// Do this
34+
foo: string | null
35+
36+
// Or this, if there always will be a value there
37+
foo: string
38+
}
39+
40+
export const state: State = {
41+
foo: null
42+
}
43+
```
44+
{% endhint %}
45+
2246
### Naming
2347

2448
Each value needs to sit behind a name. Naming can be difficult, but we have some help. Even though we eventually do want to consume our application through a user interface we ideally want to avoid naming things specifically related to the environment where we show the user interface. Things like **page**, **tabs**, **modal** etc. are specific to a browser experience, maybe related to a certain size. We want to avoid those names as they should not dictate which elements are to be used with the state, that is up to the user interface to decide later. So here are some generic terms to use instead:
@@ -31,7 +55,7 @@ Each value needs to sit behind a name. Naming can be difficult, but we have some
3155

3256
The root value of your state tree is an object, because objects are great for holding other values. An object has keys that point to values. Most of these keys point to values that are the actual state of the application, but these keys can also represent domains of the application. A typical state structure could be:
3357

34-
```javascript
58+
```typescript
3559
{
3660
modes: ['issues', 'admin'],
3761
currentModeIndex: 0,
@@ -60,7 +84,7 @@ Arrays are similar to objects in the sense that they hold other values, but inst
6084

6185
Strings are of course used to represent text values. Names, descriptions and whatnot. But strings are also used for ids, types, etc. Strings can be used as values to reference other values. This is an important part in structuring state. For example in our **objects** example above we chose to use an array to represent the modes, using an index to point to the current mode, but we could also do:
6286

63-
```javascript
87+
```typescript
6488
{
6589
modes: {
6690
issues: 0,
@@ -91,38 +115,53 @@ All values, with the exception of booleans, can also be **null**. Non-existing.
91115

92116
A concept in Javascript called a [GETTER](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) allows you to intercept accessing a property in an object. A getter is just like a plain value, it can be added or removed at any point. Getters do **not** cache the result for that very reason, but whatever state they access is tracked.
93117

94-
{% tabs %}
95-
{% tab title="overmind/state.js" %}
96-
```javascript
97-
export const state = {
118+
{% code title="overmind/state.ts" %}
119+
```typescript
120+
export type User = {
121+
id: number
122+
firstName: string
123+
lastName: string
124+
readonly fullName: string
125+
}
126+
127+
export type State = {
128+
user: User
129+
}
130+
131+
export const state: State = {
98132
user: {
99133
id: 1,
100134
firstName: 'Bob',
101135
lastName: 'Jackson',
102-
jwt: '1234567'
103-
},
104-
get isLoggedIn() {
105-
return Boolean(this.user && this.user.jwt)
136+
get fullName(this: User) {
137+
return this.firstName + ' ' + this.lastName
138+
}
106139
}
107140
}
108141
```
109-
{% endtab %}
110-
{% endtabs %}
142+
{% endcode %}
111143

112144
### Cached getter
113145

114146
When you need to do more heavy calculation or combine state from different parts of the tree you can use a plain function instead. Overmind treats these functions like a **getter**, but the returned value is cached and they can also access the root state of the application. A simple example of this would be:
115147

116-
{% tabs %}
117-
{% tab title="overmind/state.js" %}
118148
```typescript
149+
import { Derive } from 'overmind'
150+
151+
export type State = {
152+
title: string
153+
upperTitle: Derive<State, string>
154+
}
155+
119156
export const state: State = {
120157
title: 'My awesome title',
121158
upperTitle: state => state.title.toUpperCase()
122159
}
123160
```
124-
{% endtab %}
125-
{% endtabs %}
161+
162+
{% hint style="info" %}
163+
Is is important that you define your state with a **type**, do **NOT** use an **interface**
164+
{% endhint %}
126165

127166
The first argument of the function is the state the derived function is attached to. A second argument is also passed and that is the root state of the application, allowing you to access whatever you would need. Two important traits of the derived function is:
128167

@@ -137,20 +176,31 @@ Even though derived state is defined as functions you consume them as plain valu
137176

138177
### Dynamic getter
139178

140-
Sometimes you want to derive state based on some value coming from the user interface. You can do this by creating a function that returns a function. This can be useful for helper functions:
179+
Sometimes you want to derive state based on some value coming from the user interface. You can do this by creating a function that returns a function. For example you want to be able to select records in a table and calculate some data based on that:
141180

142-
{% tabs %}
143-
{% tab title="overmind/state.js" %}
144-
```javascript
145-
export const state = {
146-
users: {},
147-
userById: ({ users }) => id => users[id]
181+
{% code title="overmind/state.ts" %}
182+
```typescript
183+
import { Derive } from 'overmind'
184+
185+
export type State = {
186+
counts: {
187+
[id: string]: number
188+
}
189+
totalCountBy: Derive<State, (ids: string[]) => number>
148190
}
149191

150-
// state.userById('123')
192+
export const state: State = {
193+
counts: {
194+
a: 2,
195+
b: 3,
196+
c: 5
197+
},
198+
totalCountBy: state => ids => ids.reduce((aggr, id) => aggr + state.counts[id], 0)
199+
}
200+
201+
// state.totalCountBy(['a', 'b'])
151202
```
152-
{% endtab %}
153-
{% endtabs %}
203+
{% endcode %}
154204

155205
## References
156206

@@ -159,18 +209,32 @@ When you add objects and arrays to your state tree, they are labeled with an “
159209
So this is an example of how you would **not** want to do it:
160210

161211
{% tabs %}
162-
{% tab title="overmind/state.js" %}
212+
{% tab title="overmind/state.ts" %}
163213
```typescript
164-
export const state = {
214+
export type User = {
215+
id: string
216+
username: string
217+
}
218+
219+
export type State = {
220+
users: {
221+
[id: string]: User
222+
}
223+
currentUser: User
224+
}
225+
226+
export const state: State = {
165227
users: {},
166228
currentUser: null
167229
}
168230
```
169231
{% endtab %}
170232

171-
{% tab title="overmind/actions.js" %}
233+
{% tab title="overmind/actions.ts" %}
172234
```typescript
173-
export const setUser = ({ state }, id) => {
235+
import { Action } from 'overmind'
236+
237+
export const setUser: Action<string> = ({ state }, id) => {
174238
state.currentUser = state.users[id]
175239
}
176240
```
@@ -180,21 +244,36 @@ export const setUser = ({ state }, id) => {
180244
You’d rather have a reference to the user id, and for example use a **getter** to grab the actual user:
181245

182246
{% tabs %}
183-
{% tab title="overmind/state.js" %}
247+
{% tab title="overmind/state.ts" %}
184248
```typescript
185-
export const state = {
249+
export type User = {
250+
id: string
251+
username: string
252+
}
253+
254+
export type State = {
255+
users: {
256+
[id: string]: User
257+
}
258+
currentUserId: string
259+
currentUser: User
260+
}
261+
262+
export const state: State = {
186263
users: {},
187264
currentUserId: null,
188-
get currentUser(this) {
265+
get currentUser(this: State) {
189266
return this.users[this.currentUserId]
190267
}
191268
}
192269
```
193270
{% endtab %}
194271

195-
{% tab title="overmind/actions.js" %}
272+
{% tab title="overmind/actions.ts" %}
196273
```typescript
197-
export const setUser = ({ state }, id) => {
274+
import { Action } from 'overmind'
275+
276+
export const setUser: Action<string> = ({ state }, id) => {
198277
state.currentUserId = id
199278
}
200279
```
@@ -205,31 +284,43 @@ export const setUser = ({ state }, id) => {
205284

206285
We define the state of the application in **state** files. For example, the top level state could be defined as:
207286

208-
{% tabs %}
209-
{% tab title="overmind/state.js" %}
210-
```javascript
211-
export const state = {
287+
{% code title="overmind/state.ts" %}
288+
```typescript
289+
export type User = {
290+
username: string
291+
bio: string
292+
}
293+
294+
export type State = {
295+
isLoading: boolean
296+
user: User
297+
}
298+
299+
export const state: State = {
212300
isLoading: false,
213301
user: null
214302
}
215303
```
216-
{% endtab %}
217-
{% endtabs %}
304+
{% endcode %}
218305

219306
To expose the state on the instance you can follow this recommended pattern:
220307

221308
{% tabs %}
222-
{% tab title="overmind/index.js" %}
309+
{% tab title="overmind/index.ts" %}
223310
```typescript
224311
import { state } from './state'
225312

226313
export const config = {
227314
state
228315
}
316+
317+
declare module 'overmind' {
318+
interface Config extends IConfig<typeof config> {}
319+
}
229320
```
230321
{% endtab %}
231322

232-
{% tab title="index.js" %}
323+
{% tab title="index.ts" %}
233324
```typescript
234325
import { createOvermind } from 'overmind'
235326
import { config } from './overmind'
@@ -239,10 +330,6 @@ const overmind = createOvermind(config)
239330
{% endtab %}
240331
{% endtabs %}
241332

242-
{% hint style="info" %}
243-
For scalability you can define **namespaces** for multiple configurations. Read more about that in [Structuring the app](../guides-1/structuring-the-app.md)
244-
{% endhint %}
245-
246333
## Summary
247334

248335
This short guide gave you some insight into how we think about state and what state really is in an application. There is more to learn about these values and how to use them to describe the application. Please move on to other guides to learn more.

core/going-functional.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ You get very far building your application with straightforward imperative actio
55
Let us look at a concrete example of how messy an imperative approach would be compared to a functional approach.
66

77
{% tabs %}
8-
{% tab title="overmind/actions.ts" %}
8+
{% tab title="overmind/actions.js" %}
99
```typescript
10-
import { Action } from 'overmind'
11-
1210
let debounce
13-
export const search: Action<Event> = ({ state, effects }, event) => {
11+
export const search = ({ state, effects }, event) => {
1412
state.query = event.currentTarget.value
1513

1614
if (query.length < 3) return

core/typescript.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -108,28 +108,6 @@ export const state: State = {
108108
It is important that you use a **type** and not an **interface.** This has to do with the way Overmind resolves the state typing. ****
109109
{% endhint %}
110110

111-
When writing Typescript you should **not** use optional values for your state \(**?**\), or use **undefined** in a union type. In a serializable state store world **null** is the value indicating _“there is no value”._
112-
113-
```typescript
114-
type State = {
115-
// Do not do this
116-
foo?: string
117-
118-
// Do not do this
119-
foo: string | undefined
120-
121-
// Do this
122-
foo: string | null
123-
124-
// Or this, if there always will be a value there
125-
foo: string
126-
}
127-
128-
export const state: State = {
129-
foo: null
130-
}
131-
```
132-
133111
### Getter
134112

135113
{% tabs %}

0 commit comments

Comments
 (0)