Skip to content

Commit 2754e37

Browse files
christianalfonigitbook-bot
authored andcommitted
GitBook: [v28] 44 pages modified
1 parent fbc1678 commit 2754e37

24 files changed

+562
-904
lines changed

README.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ description: frictionless state management
44

55
# Overmind
66

7-
> Web application development is about **defining**, **changing** and **consuming state** to produce a user experience. Overmind aims for a developer experience where that is all you focus on, reducing the orchestration of state management to a minimum. Making you a **happier** and more **productive** developer!
7+
[![GitHub stars](https://img.shields.io/github/stars/cerebral/overmind.svg?style=social&label=Star&maxAge=2592000)](https://github.com/cerebral/overmind/stargazers/)
88

9-
{% embed url="https://overmindjs.changefeed.app/general/v26" caption="" %}
9+
> Web application development is about **defining**, **changing** and **consuming state** to produce a user experience. Overmind aims for a developer experience where that is all you focus on, reducing the orchestration of state management to a minimum. Making you a **happier** and more **productive** developer
10+
11+
{% embed url="https://overmindjs.changefeed.app/general/v28" %}
1012

1113
## APPLICATION INSIGHT
1214

@@ -57,7 +59,7 @@ export const loadApp = ({ state, effects }) => {
5759

5860
## SAFE AND PREDICTABLE CHANGES
5961

60-
When you build applications that perform many state changes things can get out of hand. In Overmind you can only perform state changes from **actions** and all changes are tracked by the development tool. Even effects are tracked and reactions are tracked.
62+
When you build applications that perform many state changes things can get out of hand. In Overmind you can only perform state changes from **actions** and all changes are tracked by the development tool. Even effects and reactions are tracked.
6163

6264
```javascript
6365
export const getItems = async ({ state, effects }) => {
@@ -69,37 +71,34 @@ export const getItems = async ({ state, effects }) => {
6971

7072
## COMPLEXITY TOOLS
7173

72-
Even though Overmind can create applications with plain **state** and **actions**, you can use **opt-in** tools like **functional operators**,**, statemachines** and state values defined as a **class,** to manage complexities of your application.
74+
Even though Overmind can create applications with plain **state** and **actions**, you can use **opt-in** tools like **functional operators**, **statemachines** and state values defined as a **class,** to manage complexities of your application.
7375

7476
{% tabs %}
7577
{% tab title="Operators" %}
7678
```javascript
7779
export const search = pipe(
78-
mutate(({ state }, query) => {
80+
({ state }, query) => {
7981
state.query = query
80-
}),
82+
},
8183
filter((_, query) => query.length > 2),
8284
debounce(200),
83-
mutate(async ({ state, effects }, query) => {
85+
async ({ state, effects }, query) => {
8486
state.isSearching = true
8587
state.searchResult = await effects.getSearchResult(query)
8688
state.isSearching = false
87-
})
89+
}
8890
)
8991
```
9092
{% endtab %}
9193

9294
{% tab title="Statemachines" %}
9395
```typescript
9496
export const state = statemachine({
95-
unauthenticated: ['authenticating'],
96-
authenticating: ['unauthenticated', 'authenticated'],
97-
authenticated: ['unauthenticating'],
98-
unauthenticating: ['unauthenticated', 'authenticated']
99-
}, {
100-
state: 'unauthenticated',
101-
user: null,
102-
error: null
97+
SIGN_IN: (state) => {
98+
if (state.current === 'UNAUTHENTICATED') {
99+
return { current: 'AUTHENTICATING' }
100+
}
101+
}
103102
})
104103
```
105104
{% endtab %}

addons/graphql.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const config = {
3131

3232
{% tab title="overmind/state.js" %}
3333
```typescript
34+
3435
export const state = {
3536
posts: []
3637
}
@@ -182,7 +183,7 @@ export const getPosts = async ({ state, effects, actions }) => {
182183
const { posts } = await effects.gql.queries.posts()
183184

184185
state.posts = posts
185-
186+
186187
effects.gql.subscriptions.onPostAdded(actions.onPostAdded)
187188
}
188189

@@ -235,7 +236,7 @@ export const addPost = async ({ state, effects }, title) => {
235236

236237
There are two points of options in the Graphql factory. The **headers** and the **options**.
237238

238-
The headers option is a function which receives the state of the application. That means you can produce request headers dynamically. This can be useful related to authentciation.
239+
The headers option is a function that runs on every request, meaning you can dynamically change the headers based on the state of the application.
239240

240241
{% tabs %}
241242
{% tab title="overmind/onInitialize.js" %}

api-1/createovermindmock.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,26 @@ describe('Actions', () => {
4848
It is important that you separate your **config** from the instantiation of Overmind, meaning that **createOvermind** should not be used in the same file as the config you see imported here, it should rather be used where you render your application. This allows the config to be used for multiple purposes.
4949
{% endhint %}
5050

51+
## Setting initial state
52+
53+
Pass a function as the second or third argument to set initial state.
54+
55+
{% tabs %}
56+
{% tab title="overmind/actions.test.js" %}
57+
```typescript
58+
import { createOvermindMock } from 'overmind'
59+
import { config } from './'
60+
61+
describe('State', () => {
62+
test('should derive authors of posts', async () => {
63+
const overmind = createOvermindMock(config, (state) => {
64+
state.posts = { 1: { id: 1, author: 'Janet' } }
65+
})
66+
67+
expect(overmind.state.authors).toEqual(['Janet'])
68+
})
69+
})
70+
```
71+
{% endtab %}
72+
{% endtabs %}
73+

api-1/statemachine.md

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,61 @@
22

33
A statematchine allows you to wrap state with transitions. That means you can protect your logic from running in invalid states of the application.
44

5+
Please read the [**statemachine guide**](../guides-1/using-state-machines.md) ****to learn more about statemachines and typing them.
6+
57
## Create a statemachine
68

79
You define a whole namespace as a statemachine, you can have a nested statemachine or you can even put statemachines inside statemachines.
810

911
```javascript
1012
import { statemachine } from 'overmind'
1113

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+
}
1818
})
1919
```
2020

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.
2222

23-
As actual state values we define the initial transition state of **UNAUTHENTICATED**.
23+
## Instantiate machine
2424

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.
2626

2727
```javascript
28-
import { statemachine } from 'overmind'
28+
import { machine } from './myMachine'
2929

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+
}
3933
```
4034

4135
## Transition between states
4236

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.
4440

4541
```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)
5444
}
5545
```
5646

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.
5850

5951
```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+
}
7358
}
7459
```
7560

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.
7962

core/defining-state.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const state = {
113113
The returned value here is indeed a function you call. The cool thing is that the function itself will never change, but whatever state you access when calling the function will be tracked by the caller of the function. So for example if a component uses **getUserById** during rendering it will track what is accessed in the function and continue tracking whatever you access on the returned value.
114114

115115
{% hint style="info" %}
116-
You may use a derived for all sorts of calculations. But sometimes it's better to just use a plain action to manipulate some state than using a derived. Why? Imagine a table component having a lot of rows and columns. We assume the table component also takes care of sorting and filtering and is capable of adding new rows. Now if you solve the sorting and filtering using a derived the following could happen: User adds a new row but it is not displayed in the list because the derived immediately kicked in and filtered it out. Thats not a good user experience. Also in this case the filtering and sorting is clearly started by a simple user interaction \(setting a filter value, clicking on a column,...\) so why not just start an action which creates the new list of sorted and filtered keys? Also the heavy calculation is now very predictable and doesn't cause performance issues because the derived kickes in too often \(Because it could have many dependencies you might didn't think of\)
116+
You may use a derived for all sorts of calculations. But sometimes it's better to just use a plain action to manipulate some state than using a derived. Why? Imagine a table component having a lot of rows and columns. We assume the table component also takes care of sorting and filtering and is capable of adding new rows. Now if you solve the sorting and filtering using a derived the following could happen: User adds a new row but it is not displayed in the list because the derived immediately kicked in and filtered it out. Thats not a good user experience. Also in this case the filtering and sorting is clearly started by a simple user interaction \(setting a filter value, clicking on a column,...\) so why not just start an action which creates the new list of sorted and filtered keys? Also the heavy calculation is now very predictable and doesn't cause performance issues because the derived kickes in too often \(Because it could have many dependencies you did not think of\)
117117
{% endhint %}
118118

119119
### Class instances
@@ -153,7 +153,7 @@ export const state = {
153153
{% endtabs %}
154154

155155
{% hint style="warning" %}
156-
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
156+
It is important 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
157157
{% endhint %}
158158

159159
You can now use this instance as normal and of course create new ones.
@@ -197,7 +197,7 @@ The **SERIALIZE** symbol will not be part of the actual serialization done with
197197

198198
#### Rehydrating classes
199199

200-
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:
200+
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:
201201

202202
{% tabs %}
203203
{% tab title="overmind/actions.js" %}
@@ -321,7 +321,7 @@ const state = {
321321

322322
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.
323323

324-
To properly understand state machines, please read the guide [**Using state machines**](../guides-1/using-state-machines.md).
324+
To properly understand state machines, please read the guide [**Using state machines**](../guides-1/using-state-machines.md).
325325

326326
## References
327327

core/devtools.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ If you are using the **Insiders** version of VSCode the extension will not work.
5151
When you create your application it will automatically connect through **localhost:3031**, meaning that everything should just work out of the box. If you need to change the port, connect the application over a network \(mobile development\) or similar, you can configure how the application connects:
5252

5353
```javascript
54-
import { createOvermind } from 'overmind'
54+
import { createOvermind } from 'overmind'
5555
import { config } from './overmind'
5656

5757
const overmind = createOvermind(config, {
@@ -63,7 +63,7 @@ const overmind = createOvermind(config, {
6363

6464
ChromeOS does not expose localhost as normal. That means you need to connect with **penguin.termina.linux.test:3031**, or you can use the following plugin to forward **localhost:**
6565

66-
{% embed url="https://chrome.google.com/webstore/detail/connection-forwarder/ahaijnonphgkgnkbklchdhclailflinn/related?hl=en-US" caption="" %}
66+
{% embed url="https://chrome.google.com/webstore/detail/connection-forwarder/ahaijnonphgkgnkbklchdhclailflinn/related?hl=en-US" %}
6767

6868
## Hot Module Replacement
6969

@@ -104,6 +104,8 @@ import { Provider } from 'overmind-react'
104104
import { App } from './components/App'
105105

106106
render(<Provider value={overmind}><App /></Provider>, document.querySelector('#app'))
107+
108+
107109
```
108110
{% endtab %}
109111

0 commit comments

Comments
 (0)