Skip to content

Commit 0a60805

Browse files
christianalfonigitbook-bot
authored andcommitted
GitBook: [master] 4 pages modified
1 parent e55df06 commit 0a60805

File tree

4 files changed

+167
-15
lines changed

4 files changed

+167
-15
lines changed

core/defining-state.md

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Even though you can use **getters** as normal, they do not cache like **derived*
114114

115115
### Serializing class values
116116

117-
If you have an application that needs to serialize the state, for example to local storage or server side rendering, you can still use class instances with Overmind. This works out of the box except when you add the **toJSON** method to a class. In that case you will need to add a symbol called **SERIALIZE**:
117+
If you have an application that needs to serialize the state, for example to local storage or server side rendering, you can still use class instances with Overmind. By default you really do not have to do anything, but if you use **Typescript** or you choose to use **toJSON** on your classesOvermind exposes a symbol called **SERIALIZE** that you can attach to your class.
118118

119119
```typescript
120120
import { SERIALIZE } from 'overmind'
@@ -178,11 +178,12 @@ Since our user is a class instance we can tell rehydrate what to do, where it is
178178
import { SERIALIZE } from 'overmind'
179179

180180
class User {
181+
[SERIALIZE]
181182
constructor() {
182183
this.username = ''
183184
this.jwt = ''
184185
}
185-
fromJSON(json) {
186+
static fromJSON(json) {
186187
return Object.assign(new User(), json)
187188
}
188189
}
@@ -221,7 +222,7 @@ That means the following will behave as expected:
221222
import { User } from './models'
222223

223224
export const state = {
224-
user: null, // Expecting an instance or null
225+
user: null, // Can be existing class instance or null
225226
usersList: [], // Expecting an array of values
226227
usersDictionary: {} // Expecting a dictionary of values
227228
}
@@ -261,23 +262,132 @@ export const updateState = ({ state }) => {
261262
Note that **rehydrate** gives you full type safety when adding the **SERIALIZE** symbol to your classes. This is a huge benefit as Typescript will yell at you when the state structure changes, related to the rehydration
262263
{% endhint %}
263264

264-
## Naming
265+
## Statemachines
265266

266-
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:
267+
Very often you get into a situation where you define states as **isLoading**, **hasError** etc. Having these kinds of state can cause **impossible states**. For example:
267268

269+
```typescript
270+
const state = {
271+
isAuthenticated: true,
272+
isAuthenticating: true
273+
}
274+
```
268275

276+
You can not be authenticating and be authenticated at the same time. This kind of logic very often causes bugs in applications. That is why Overmind allows you to define statemachines. It sounds complicated, but is actually very simple.
269277

270-
{% hint style="info" %}
278+
### Defining
271279

272-
{% endhint %}
280+
{% tabs %}
281+
{% tab title="overmind/state.js" %}
282+
```typescript
283+
import { statemachine } from 'overmind'
273284

274-
{% hint style="info" %}
285+
export const state = {
286+
mode: statemachine({
287+
initial: 'unauthenticated',
288+
states: {
289+
unauthenticated: ['authenticating'],
290+
authenticating: ['unauthenticated', 'authenticated'],
291+
authenticated: ['unauthenticating'],
292+
unauthenticating: ['unauthenticated', 'authenticated']
293+
}
294+
}),
295+
user: null,
296+
error: null
297+
}
298+
```
299+
{% endtab %}
300+
{% endtabs %}
301+
302+
### Transitioning
303+
304+
{% tabs %}
305+
{% tab title="overmind/actions.js" %}
306+
```typescript
307+
export const login = async ({ state, effects }) => {
308+
return state.mode.authenticating(async () => {
309+
try {
310+
const user = await effects.api.getUser()
311+
state.mode.authenticated(() => {
312+
state.user = user
313+
})
314+
} catch (error) {
315+
state.mode.unauthenticated(() => {
316+
state.error = error
317+
})
318+
}
319+
})
320+
}
321+
322+
export const logout = async ({ state, effects }) => {
323+
return state.mode.unauthenticating(async () => {
324+
try {
325+
await effects.api.logout()
326+
state.mode.unauthenticated()
327+
} catch (error) {
328+
state.mode.authenticated(() => {
329+
state.error = error
330+
})
331+
}
332+
})
333+
}
334+
```
335+
{% endtab %}
336+
{% endtabs %}
275337

338+
{% hint style="info" %}
339+
If your transition runs asynchronously you should return the transition to ensure that the action execution is tracked
276340
{% endhint %}
277341

278-
* page: **mode**
279-
* tabs: **sections**
280-
* modal: **editUser.active**
342+
What is important to realize here is that our logic is separated into **allowable** transitions. That means when we are waiting for the user on **line 4** and some other logic has changed the state to **unauthenticated** in the meantime, the user will not be set, as the **authenticated** transition is now not possible. This is what state machines do. They group logic into states that are allowed to run, preventing invalid logic to run.
343+
344+
### Current state
345+
346+
The current state is accessed, related to this example, by:
347+
348+
```typescript
349+
state.mode.current
350+
```
351+
352+
### Exit
353+
354+
It is also possible to run logic when a transition exits. An example of this is for example if a transition sets up a subscription. This subscription can be disposed when the transition is exited.
355+
356+
{% tabs %}
357+
{% tab title="overmind/actions.js" %}
358+
```typescript
359+
export const login = async ({ state, effects }) => {
360+
return state.mode.authenticating(async () => {
361+
try {
362+
const user = await effects.api.getUser()
363+
let disposeSubscription
364+
state.mode.authenticated(
365+
() => {
366+
disposeSubscription = effects.api.subscribeNotifications()
367+
state.user = user
368+
},
369+
() => {
370+
disposeSubscription()
371+
}
372+
)
373+
} catch (error) {
374+
state.mode.unauthenticated(() => {
375+
state.error = error
376+
})
377+
}
378+
})
379+
}
380+
```
381+
{% endtab %}
382+
{% endtabs %}
383+
384+
### Reset
385+
386+
You can reset the state of a statemachine, which also runs the exit of the current transition:
387+
388+
```typescript
389+
state.mode.reset()
390+
```
281391

282392
## Undefined
283393

core/typescript.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,43 @@ export const filterAwesome: <T extends { isAwesome: boolean }>() => Operator<T>
387387

388388
That means this operator can handle any type that matches an **isAwesome** property, though will pass the original type through.
389389

390-
## Statecharts
390+
## Statemachine
391+
392+
Statemachines exposes a type called **Statemachine**, though it is strictly not necessary to use as it is recommended to separate your machine definition from your state using the factory:
393+
394+
{% tabs %}
395+
{% tab title="overmind/state.ts" %}
396+
```typescript
397+
import {statemachine } from 'overmind'
398+
399+
type Modes =
400+
| 'unauthenticated'
401+
| 'authenticating'
402+
| 'authenticated'
403+
| 'unauthenticating'
404+
405+
const mode = statemachine<Modes>({
406+
initial: 'unauthenticated',
407+
states: {
408+
unauthenticated: ['authenticating'],
409+
authenticating: ['unauthenticated', 'authenticated'],
410+
authenticated: ['unauthenticating'],
411+
unauthenticating: ['unauthenticated', 'authenticated']
412+
}
413+
})
414+
415+
type State = {
416+
mode: typeof mode
417+
}
418+
419+
export const state: State = {
420+
mode
421+
}
422+
```
423+
{% endtab %}
424+
{% endtabs %}
425+
426+
## Statechart
391427

392428
To type a statechart you use the **Statechart** type:
393429

faq.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ First… try to refresh your app to reconnect. If this does not work make sure t
88

99
Restart VS Code
1010

11+
## My operator actions are not running?
12+
13+
Operators are identified with a Symbol. If you happen to use Overmind across packages you might be running two versions of Overmind. The same goes for core package and view package installed out of version sync. Make sure you are only running on package of Overmind by looking into your **node\_modules** folder.
14+
1115

1216

views/angular.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { AppComponent } from './app.component';
3939
declarations: [ AppComponent ],
4040
bootstrap: [ AppComponent ],
4141
providers: [
42-
{ provide: OVERMIND_INSTANCE, useValue: createOvermind(config) },
42+
{ provide: OVERMIND_INSTANCE, useFactory: () => createOvermind(config) },
4343
{ provide: Store, useExisting: OvermindService }
4444
]
4545
})
@@ -85,7 +85,9 @@ if (environment.production) {
8585
platformBrowserDynamic()
8686
.bootstrapModule(AppModule, {
8787
// We do not need zones, we rather use the tracking
88-
// directive, which gives us a huge optimization
88+
// directive, which gives us a pretty signifcant performance
89+
// boost. Note that 3rd party libraries might need ngZone,
90+
// in which case you can not set it to "noop"
8991
ngZone: "noop"
9092
})
9193
.catch(err => console.log(err));
@@ -124,7 +126,7 @@ You can now access the **admin** state and actions directly with **state** and *
124126

125127
## NgZone
126128

127-
Since Overmind knows when your components should update you can safely turn **ngZone** to `"noop"`. Note that other 3rd party libraries may not support this.
129+
The Overmind **\*track** directive knows when your components should update, and so is much more efficient at change detection than Angular's default NgZone. In order to take advantage of the efficiency provided by the \***track** directive, you _must_ set **ngZone** to "noop". Note that other 3rd party libraries may not support this. If for any reason you can't set **ngZone** to "noop", then the \***track** directive is redundant, and you can safely exclude it from your templates.
128130

129131
## Rendering
130132

0 commit comments

Comments
 (0)