You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: core/defining-state.md
+43-80Lines changed: 43 additions & 80 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,29 +6,23 @@ The mechanism of communicating from the application to the user interface is cal
6
6
7
7

8
8
9
-
## Core values
9
+
## State tree
10
10
11
-
In JavaScript we can create all sorts of abstractions to describe values, but in Overmind we lean on the core serializable values. These are **objects**, **arrays**, **strings**, **numbers**, **booleans** and **null**. Serializable values means that we can easily convert the state into a string and back again. This is fundamental for creating great developer experiences, passing state between client and server and other features. You can describe any application state with these core values.
12
-
13
-
Let us talk a little bit about what each value helps us represent in our application.
14
-
15
-
### Objects
16
-
17
-
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:
11
+
Overmind is structured as a single state tree. That means all of your state can be accessed through a single object, called the **state**. This state tree will hold values which describes different states of your application. The tree branches out using plain objects. That means you can consider plain objets as **branches** of your state tree.
18
12
19
13
```javascript
20
-
{
14
+
{// branch
21
15
modes: ['issues', 'admin'],
22
16
currentModeIndex:0,
23
-
admin: {
17
+
admin: {// branch
24
18
currentUserId:null,
25
-
users: {
19
+
users: {// branch
26
20
isLoading:false,
27
21
data: {},
28
22
error:null
29
23
},
30
24
},
31
-
issues: {
25
+
issues: {// branch
32
26
sortBy:'name',
33
27
isLoading:false,
34
28
data: {},
@@ -37,6 +31,14 @@ The root value of your state tree is an object, because objects are great for ho
37
31
}
38
32
```
39
33
34
+
## State tree values
35
+
36
+
The following are values to be used with the state tree.
37
+
38
+
### Objects
39
+
40
+
The plain objects are what **branches** out the tree. It is not really considered a value in itself, it is a state branch holding values.
41
+
40
42
### Arrays
41
43
42
44
Arrays are similar to objects in the sense that they hold other values, but instead of keys pointing to values you have indexes. That means it is ideal for iteration. But more often than not objects are actually better at managing lists of values. We can actually do fine without arrays in our state. It is when we produce the actual user interface that we usually want arrays. You can learn more about this in the [MANAGING LISTS](../guides-1/managing-lists.md) guide.
@@ -70,14 +72,28 @@ Are things loading or not, is the user logged in or not? These are typical uses
70
72
71
73
All values, with the exception of booleans, can also be **null**. Non-existing. You can have a non-existing object, array, string or number. It means that if we haven’t selected a mode, both the string version and number version would have the value **null**.
72
74
73
-
##Undefined
75
+
### Derived
74
76
75
-
You might wonder why **undefined**is not part of the core value types. Well, there are two reasons:
77
+
When you need to derive state you can add a function to your tree. 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:
76
78
77
-
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
78
-
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
79
+
{% tabs %}
80
+
{% tab title="overmind/state.js" %}
81
+
```typescript
82
+
exportconst state:State= {
83
+
title: 'My awesome title',
84
+
upperTitle: state=>state.title.toUpperCase()
85
+
}
86
+
```
87
+
{% endtab %}
88
+
{% endtabs %}
89
+
90
+
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.
91
+
92
+
{% hint style="info" %}
93
+
Even though derived state is defined as functions you consume them as plain values. You do not have to call the derived function to get the value. Derived functions can also be dynamically added.
94
+
{% endhint %}
79
95
80
-
## Class values
96
+
###Class instances
81
97
82
98
Overmind also supports using class instances as state values. Depending on your preference this can be a powerful tool to organize your logic. What classes provide is a way to co locate state and logic for changing and deriving that state. In functional programming the state and the logic is separated and it can be difficult to find a good way to organize the logic operating on that state.
83
99
@@ -91,7 +107,7 @@ class LoginForm {
91
107
this.username=''
92
108
this.password=''
93
109
}
94
-
isValid() {
110
+
getisValid() {
95
111
returnBoolean(this.username&&this.password)
96
112
}
97
113
reset() {
@@ -114,16 +130,12 @@ export const state = {
114
130
{% endtabs %}
115
131
116
132
{% hint style="warning" %}
117
-
It is import that you do **NOT** use arrow functions on your methods. The reason is that this binds the context of the method to the instance itself, meaning that Overmind is unable to proxy access and allow you to do tracked mutations
133
+
It is import that you do **NOT** use arrow functions on your methods. The reason is that this binds the context of the method to the instance itself, meaning that Overmind is unable to proxy access and track mutations
118
134
{% endhint %}
119
135
120
136
You can now use this instance as normal and of course create new ones.
121
137
122
-
{% hint style="info" %}
123
-
Even though you can use **getters** as normal, they do not cache like **derived**. **Derived** is a concept of the state tree itself. It is unlikely that you need heavy computation within a single class instance though, it is typically across class instances, where **derived** fits the bill
124
-
{% endhint %}
125
-
126
-
### Serializing class values
138
+
#### Serializing class values
127
139
128
140
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.
129
141
@@ -160,7 +172,7 @@ class User {
160
172
The **SERIALIZE** symbol will not be part of the actual serialization done with **JSON.stringify**
161
173
{% endhint %}
162
174
163
-
### Rehydrating classes
175
+
####Rehydrating classes
164
176
165
177
The [**rehydrate**](../api-1/rehydrate.md)****utility of Overmind allows you to rehydrate state either by a list of mutations or a state object, like the following:
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
274
286
{% endhint %}
275
287
276
-
## Getters
277
-
278
-
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 and runs whenever the property is accessed.
279
-
280
-
{% tabs %}
281
-
{% tab title="overmind/state.js" %}
282
-
```javascript
283
-
exportconststate= {
284
-
user: {
285
-
id:1,
286
-
firstName:'Bob',
287
-
lastName:'Jackson',
288
-
jwt:'1234567'
289
-
},
290
-
getisLoggedIn() {
291
-
returnBoolean(this.user&&this.user.jwt)
292
-
}
293
-
}
294
-
```
295
-
{% endtab %}
296
-
{% endtabs %}
297
-
298
-
## Derived
299
-
300
-
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:
301
-
302
-
{% tabs %}
303
-
{% tab title="overmind/state.js" %}
304
-
```typescript
305
-
exportconst state:State= {
306
-
title: 'My awesome title',
307
-
upperTitle: state=>state.title.toUpperCase()
308
-
}
309
-
```
310
-
{% endtab %}
311
-
{% endtabs %}
312
-
313
-
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. You can add a derived dynamically in an action as well.
314
-
315
-
{% hint style="info" %}
316
-
Even though derived state is defined as functions you consume them as plain values. You do not have to call the derived function to get the value.
317
-
{% endhint %}
318
-
319
-
## Statemachines
288
+
### Statemachines
320
289
321
290
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:
322
291
@@ -329,7 +298,7 @@ const state = {
329
298
330
299
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.
331
300
332
-
### Defining
301
+
####Defining
333
302
334
303
{% tabs %}
335
304
{% tab title="overmind/state.js" %}
@@ -355,13 +324,7 @@ export const state = {
355
324
356
325
You set an **initial** state and then you create a relationship between the different states and what states they can transition into. So when **unauthenticated** is the state, only logic triggered with an **authenticating** transition will run, any other transition triggered will not run its logic.
357
326
358
-
359
-
360
-
##
361
-
362
-
##
363
-
364
-
### Transitioning
327
+
#### Transitioning
365
328
366
329
{% tabs %}
367
330
{% tab title="overmind/actions.js" %}
@@ -406,15 +369,15 @@ There are two important rules for predictable transitions:
406
369
407
370
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.
408
371
409
-
### Current state
372
+
####Current state
410
373
411
374
The current state is accessed, related to this example, by:
412
375
413
376
```typescript
414
377
state.mode.current
415
378
```
416
379
417
-
### Exit
380
+
####Exit
418
381
419
382
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.
0 commit comments