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: packages/overmind-website/guides/intermediate/06_statecharts.md
+48-30Lines changed: 48 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@
3
3
Just like [operators](/guides/intermediate/04_goingfunctional) is a declarative abstraction over plain actions, **statecharts** is a declarative abtraction over an Overmind configuration of **state** and **actions**. That means you will define your charts by:
@@ -14,9 +14,11 @@ There are several benefits to using statecharts:
14
14
4. Your state definition is cleaned up as your **isLoading** types of state is no longer needed
15
15
5. You have a tool to do "top down" implementation instead of "bottom up"
16
16
17
-
You can basically think of a statechart as a way of limiting what actions are available to be executed in certain transition states. This concept is very old and was originally used to design machines where the user was exposed to all points of interaction, all buttons and switches, at any time. Statecharts would help make sure that at certain transition states certain buttons and switches would not operate.
17
+
You can basically think of a statechart as a way of limiting what actions are available to be executed in certain states of the application. This concept is very old and was originally used to design machines where the user was exposed to all points of interaction, all buttons and switches, at any time. Statecharts would help make sure that at certain states certain buttons and switches would not operate.
18
18
19
-
A simple example of this is a Walkman. When the Walkman is in a **playing** transition state you should not be able to hit the **eject** button. On the web this might seem unnecessary as points of interaction is dynamic. We simply hide and/or disable buttons. But this is the exact problem. It is fragile. It is fragile because the UI implementation itself is all you depend on to prevent logic from running when it should not. A statechart is a much more resiliant way to ensure what logic can actually run in any given state.
19
+
A simple example of this is a Walkman. When the Walkman is in a **playing** state you should not be able to hit the **eject** button. On the web this might seem unnecessary as points of interaction is dynamic. We simply hide and/or disable buttons. But this is the exact problem. It is fragile. It is fragile because the UI implementation itself is all you depend on to prevent logic from running when it should not. A statechart is a much more resiliant way to ensure what logic can actually run in any given state.
20
+
21
+
In Overmind we talk about these statechart states as **transition states**.
20
22
21
23
## Defining a statechart
22
24
@@ -61,6 +63,43 @@ This approach has three benefits:
61
63
2. When typing your application the actions already has a typed input, which would not be possible with a generic **transition** action
62
64
3. It is simpler concept both in code and for your brain
63
65
66
+
What to take notice of is that the **action** causing the transition is run before the transition actually happens. That means the action runs in the context of the current transition state and any synchronous calls to another action will obey its rules. If the action does something asynchronous, like doing an HTTP request, the transition will be performed and the asynchronous logic will run in the context of the new transition state.
67
+
68
+
```js
69
+
constmyTransitionAction=async ({ actions }) => {
70
+
// I am still in the current transition state
71
+
actions.someOtherAction()
72
+
73
+
awaitPromise.resolve()
74
+
75
+
// I am in the new transition state
76
+
actions.someOtherAction()
77
+
}
78
+
```
79
+
80
+
## Nested statecharts
81
+
82
+
With a more complicated UI we can create nested statecharts. An example of this would be a workspace UI with different tabs. You only want to allow certain actions when the related tab is active. Let us explore an example:
What to take notice of in this example is that all chart states has its own **chart** property, which allows them to be nested. The nested charts has access to the same actions and state as the parent chart.
89
+
90
+
In this example we also took advantage of the **entry** and **exit** hooks of a transition state. These also points to actions. When a transition is made into the transition state, the **entry** will run. This behavior is nested. When an **exit** hook exists and a transition is made away from the transition state, it will also run. This behavior is also nested of course.
91
+
92
+
## Parallel statecharts
93
+
94
+
It is also possible to define your charts in a parallel manner. You do this by simply using an object of keys where the key represents an ID of the chart. The **chart** property on a transition state allows the same. Either a single chart or an object of multiple charts where the key represents an ID of the chart.
95
+
96
+
```js
97
+
exportdefaultstatechart(config, {
98
+
issues: issuesChart,
99
+
projects: projectsChart
100
+
})
101
+
```
102
+
64
103
## Conditions
65
104
66
105
In our chart above we let the user log in even though there is no **username** or **password**. That seems a bit silly. In statecharts you can define conditions. These conditions receives the state of the configuration and returns true or false.
@@ -86,7 +125,7 @@ As you can see we have no state indicating that we have received an error, like
86
125
password:'',
87
126
user:null,
88
127
authenticationError:null,
89
-
states: [['ROOT_CHART', 'LOGIN']],
128
+
states: [['CHART', 'LOGIN']],
90
129
actions: {
91
130
changeUsername:true,
92
131
changePassword:true,
@@ -97,11 +136,13 @@ As you can see we have no state indicating that we have received an error, like
97
136
}
98
137
```
99
138
100
-
The **states** state is the current transition states. It is defined as an array of arrays. This indicates that we can have parallel and nested charts.
139
+
The **states** state is the current transition states. It is defined as an array of arrays. This indicates that we can have parallel and nested charts. The **CHART** symbol in the array indicates that you have defined an immediate chart. If you rather defined parallel charts you would define your own ids.
101
140
102
141
The **actions** state is a derived state. That means it automatically updates based on the current state of the chart. This is helpful for your UI implementation. It can use it to disable buttons etc. to help the user understand when certain actions are possible.
103
142
104
-
There is also a third derived state called **matches**. This derived state returns a function that allows you to figure out what state you are in:
143
+
## Identifying states
144
+
145
+
There is also a third derived state called **matches**. This derived state returns a function that allows you to figure out what state you are in. This is also the API you use in your components to identify the state of your application:
What to take notice of here is that with traditional Overmind we would most likely just set the **user** or the **authenticationError** directly in the **login** action. That is not the case with statcharts because our actions are the triggers for transitions. That means whenever we want to deal with transitions we create an action for it, even completely empty actions like **tryAgain**. This simplifies our chart definition and also we avoid having a generic **transition** action that would not be typed in TypeScript etc.
125
166
126
-
## Nested statecharts
127
-
128
-
With a more complicated UI we can create nested statecharts. An example of this would be a workspace UI with different tabs. You only want to allow certain actions when the related tab is active. Let us explore an example:
What to take notice of in this example is that all chart states has its own **charts** property, which allows them to be nested. The nested charts has access to the same actions and state as the parent chart.
135
-
136
-
In this example we also took advantage of the **entry** and **exit** hooks of a transition state. These also points to actions. When a transition is made into the transition state, the **entry** will run. This behavior is nested. When an exit hook exists and a transition is made away from the transition state, it will also run. This behavior is also nested of course.
137
-
138
-
## Parallel statecharts
139
-
140
-
It is also possible to define your charts in a parallel manner. The above example of projects and issues could have been defined as:
141
-
142
-
```js
143
-
exportdefaultstatecharts(config, {
144
-
issues: issuesChart,
145
-
projects: projectsChart
146
-
})
147
-
```
148
-
149
-
Now these two charts would operate individually. This is also the case for the **charts** property on the states of a chart.
150
167
168
+
Now these two charts would operate individually. This is also the case for the **chart** property on the states of a chart.
0 commit comments