|
2 | 2 |
|
3 | 3 | A statematchine allows you to wrap state with transitions. That means you can protect your logic from running in invalid states of the application. |
4 | 4 |
|
| 5 | +Please read the [**statemachine guide**](../guides-1/using-state-machines.md) ****to learn more about statemachines and typing them. |
| 6 | + |
5 | 7 | ## Create a statemachine |
6 | 8 |
|
7 | 9 | You define a whole namespace as a statemachine, you can have a nested statemachine or you can even put statemachines inside statemachines. |
8 | 10 |
|
9 | 11 | ```javascript |
10 | 12 | import { statemachine } from 'overmind' |
11 | 13 |
|
12 | | -export const state = statemachine({ |
13 | | - UNAUTHENTICATED: ['AUTHENTICATING'], |
14 | | - AUTHENTICATING: ['UNAUTHENTICATED', 'AUTHENTICATED'], |
15 | | - AUTHENTICATED: ['UNAUTHENTICATED'] |
16 | | -}, { |
17 | | - state: 'UNAUTHENTICATED' |
| 14 | +export const machine = statemachine({ |
| 15 | + TOGGLE: (state, payload) => { |
| 16 | + return { current: state.current === 'FOO' ? 'BAR' : 'FOO' } |
| 17 | + } |
18 | 18 | }) |
19 | 19 | ``` |
20 | 20 |
|
21 | | -Instead of only defining state, you first define a set of transitions. The key represents a transition state, here **UNAUTHENTICATED**, **AUTHENTICATING** and **AUTHENTICATED**. Then we define an array which shows the next transition state can occur in the given transition state. When **UNAUTHENTICATED** we can move into the **AUTHENTICATING** state for example. When in **AUTHENTICATING** state we can move either back to **UNAUTHENTICATED** due to an error or we might move to **AUTHENTICATED**. The point is... when you are **UNAUTHENTICATED**, you can not run logic related to being **AUTHENTICATED**. And when **AUTHENTICATING** you can not run that logic again until you are back in **UNAUTHENTICATED**. |
| 21 | +You define a statemachine by setting up the events it should manage. Each even handler decides at what current state it should run its logic. It does this by checking the **current** state. The event handler can optionally return a new state, which transitions the machine. |
22 | 22 |
|
23 | | -As actual state values we define the initial transition state of **UNAUTHENTICATED**. |
| 23 | +## Instantiate machine |
24 | 24 |
|
25 | | -If we wanted we could extend the state with other values, as normal. |
| 25 | +You instantiate a machine by calinng its **create** method. This takes the initial state and any optional base state the lives across all states of the machine. |
26 | 26 |
|
27 | 27 | ```javascript |
28 | | -import { statemachine } from 'overmind' |
| 28 | +import { machine } from './myMachine' |
29 | 29 |
|
30 | | -export const state = statemachine({ |
31 | | - UNAUTHENTICATED: ['AUTHENTICATED'], |
32 | | - AUTHENTICATING: ['UNAUTHENTICATED', 'AUTHENTICATED'], |
33 | | - AUTHENTICATED: ['UNAUTHENTICATED'] |
34 | | -}, { |
35 | | - state: 'UNAUTHENTICATED', |
36 | | - todos: {}, |
37 | | - filter: 'all' |
38 | | -}) |
| 30 | +export const state = { |
| 31 | + myMachine: machine.create({ current: 'FOO' }, { list: [] }) |
| 32 | +} |
39 | 33 | ``` |
40 | 34 |
|
41 | 35 | ## Transition between states |
42 | 36 |
|
43 | | -The transition states are also part of the resulting **state** object, in this case: |
| 37 | +You transition the state machine by sending events. |
| 38 | + |
| 39 | +That means you can send **TOGGLE** as an event to the machine. Think of sending events as actually changing the state, but in a controlled way. You can optionally pass a payload with the event. |
44 | 40 |
|
45 | 41 | ```javascript |
46 | | -// The resulting state object |
47 | | -export const state = { |
48 | | - UNAUTHENTICATED: () => {...}, |
49 | | - AUTHENTICATING: () => {...}, |
50 | | - AUTHENTICATED: () => {...}, |
51 | | - state: 'UNAUTHENTICATED', |
52 | | - todos: {}, |
53 | | - filter: 'all' |
| 42 | +export const toggle = ({ state, effects }) => { |
| 43 | + state.myMachine.send('TOGGLE', somePayload) |
54 | 44 | } |
55 | 45 | ``` |
56 | 46 |
|
57 | | -That means you can call **UNAUTHENTICATED**, **AUTHENTICATING** and **AUTHENTICATED** as functions to transition into the new states. And this is an example of how you would use them: |
| 47 | +## Matching state |
| 48 | + |
| 49 | +In actions you can check what state a machine is in by using the **matches** API. |
58 | 50 |
|
59 | 51 | ```javascript |
60 | | -export const login = ({ state, effects }) => { |
61 | | - return state.AUTHENTICATING(() => { |
62 | | - try { |
63 | | - const user = await effects.api.login() |
64 | | - return state.AUTHENTICATED(() => { |
65 | | - state.user = user |
66 | | - }) |
67 | | - } catch (error) { |
68 | | - return state.UNAUTHENTICATED(() => { |
69 | | - state.error = error |
70 | | - }) |
71 | | - } |
72 | | - }) |
| 52 | +export const toggle = ({ state, effects }) => { |
| 53 | + if (state.myMachine.matches('FOO')) { |
| 54 | + state.myMachine.send('TOGGLE', 'Cool') |
| 55 | + } else { |
| 56 | + state.myMachine.send('TOGGLE', 'Not so cool') |
| 57 | + } |
73 | 58 | } |
74 | 59 | ``` |
75 | 60 |
|
76 | | -When a component, or something else, calls the **login** action it will first try to move into the **AUTHENTICATING** state. If this is not possible, nothing else will happen. Then we go ahead an login, which returns a user. If we were to try to set the user immediately an error would be thrown, because it is being set "out of scope of the transition" \(asynchronously\). To actually set the user we first transition to **AUTHENTICATED** and given that is a valid transition the user will be set. |
77 | | - |
78 | | -What we accomplish in practice here is to ensure that changes to state is guarded by these transitions, which results in more predictable and safer code. |
| 61 | +You can also directly use `state.myMachine.current` , but please read the guide to understand the different between these two ways of checking. |
79 | 62 |
|
0 commit comments