Skip to content

Commit b934ada

Browse files
docs(website): improve effects docs
1 parent 22f5ea5 commit b934ada

File tree

8 files changed

+212
-14
lines changed

8 files changed

+212
-14
lines changed

packages/overmind-website/examples/guide/runningsideeffects/changestate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default (ts) =>
66
code: `
77
import { Action } from 'overmind'
88
9-
export const getCurrentUser: Action = async ({ effects, state }) => {
9+
export const loadApp: Action = async ({ effects, state }) => {
1010
state.currentUser = await effects.api.getCurrentUser()
1111
}
1212
`,
@@ -16,7 +16,7 @@ export const getCurrentUser: Action = async ({ effects, state }) => {
1616
{
1717
fileName: 'overmind/actions.js',
1818
code: `
19-
export const getCurrentUser = async ({ effects, state }) => {
19+
export const loadApp = async ({ effects, state }) => {
2020
state.currentUser = await effects.api.getCurrentUser()
2121
}
2222
`,

packages/overmind-website/examples/guide/runningsideeffects/class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class Api {
4444
}
4545
}
4646
47-
export const http =
47+
export const api =
4848
new Api(IS_PRODUCTION ? '/api/v1' : 'http://localhost:4321', axios)
4949
`,
5050
},
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/effects.ts',
6+
code: `
7+
import * as firebase from 'firebase'
8+
import { Post } from './state'
9+
10+
export const api = (() => {
11+
let app
12+
13+
return {
14+
initialize() {
15+
app = firebase.initializeApp(...)
16+
},
17+
async getPosts(): Promise<Post[]> {
18+
const snapshot = await app.database().ref('/posts').once('value')
19+
20+
return snapshot.val()
21+
}
22+
}
23+
})()
24+
`,
25+
},
26+
]
27+
: [
28+
{
29+
fileName: 'overmind/effects.js',
30+
code: `
31+
import * as firebase from 'firebase'
32+
33+
34+
export const api = (() => {
35+
let app
36+
37+
return {
38+
initialize() {
39+
app = firebase.initializeApp(...)
40+
},
41+
async getPosts() {
42+
const snapshot = await app.database().ref('/posts').once('value')
43+
44+
return snapshot.val()
45+
}
46+
}
47+
})()
48+
`,
49+
},
50+
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/effects.ts',
6+
code: `
7+
import { Post } from './state'
8+
9+
export const api = (() => {
10+
let app
11+
12+
return {
13+
async initialize() {
14+
const firebase = await import('firebase')
15+
16+
app = firebase.initializeApp(...)
17+
},
18+
async getPosts(): Promise<Post[]> {
19+
const snapshot = await app.database().ref('/posts').once('value')
20+
21+
return snapshot.val()
22+
}
23+
}
24+
})()
25+
`,
26+
},
27+
]
28+
: [
29+
{
30+
fileName: 'overmind/effects.js',
31+
code: `
32+
import * as firebase from 'firebase'
33+
34+
35+
export const api = (() => {
36+
let app
37+
38+
return {
39+
async initialize() {
40+
const firebase = await import('firebase')
41+
42+
app = firebase.initializeApp(...)
43+
},
44+
async getPosts() {
45+
const snapshot = await app.database().ref('/posts').once('value')
46+
47+
return snapshot.val()
48+
}
49+
}
50+
})()
51+
`,
52+
},
53+
]

packages/overmind-website/examples/guide/runningsideeffects/object.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import * as axios from 'axios'
88
import { User } from './state'
99
1010
export const api = {
11-
getCurrentUser() {
12-
return axios.get<User>('/user')
11+
async getCurrentUser() {
12+
const response = await axios.get<User>('/user')
13+
14+
return response.data
1315
}
1416
}
1517
`,
@@ -22,8 +24,10 @@ export const api = {
2224
import axios from 'axios'
2325
2426
export const http = {
25-
getCurrentUser() {
26-
return axios.get('/user')
27+
async getCurrentUser() {
28+
const response = await axios.get('/user')
29+
30+
return response.data
2731
}
2832
}
2933
`,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/onInitialize.ts',
6+
code: `
7+
import { OnInitialize } from 'overmind'
8+
9+
export const onInitialize: OnInitialize = async ({ effects }) => {
10+
effects.api.initialize()
11+
state.posts = await effects.api.getPosts()
12+
}
13+
`,
14+
},
15+
]
16+
: [
17+
{
18+
fileName: 'overmind/onInitialize.js',
19+
code: `
20+
export const onInitialize = async ({ state, effects }) => {
21+
effects.api.initialize()
22+
state.posts = await effects.api.getPosts()
23+
}
24+
`,
25+
},
26+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/onInitialize.ts',
6+
code: `
7+
import { OnInitialize } from 'overmind'
8+
9+
export const onInitialize: OnInitialize = async ({ effects }) => {
10+
await effects.api.initialize()
11+
state.posts = await effects.api.getPosts()
12+
}
13+
`,
14+
},
15+
]
16+
: [
17+
{
18+
fileName: 'overmind/onInitialize.js',
19+
code: `
20+
export const onInitialize = async ({ state, effects }) => {
21+
await effects.api.initialize()
22+
state.posts = await effects.api.getPosts()
23+
}
24+
`,
25+
},
26+
]

packages/overmind-website/guides/beginner/04_runningsideeffects.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
# Running side effects
22

3-
Developing applications is not only about managing state, but also managing side effects. A side effect is typically exampled with an http request or talking to local storage. In Overmind we just call this **effects**. It is highly encouraged that you think about your effects as an application specific API that **you** design and implement. An example of this would be:
4-
5-
```marksy
6-
h(Example, { name: "guide/runningsideeffects/changestate" })
7-
```
8-
9-
As you can see we are very specific about what the effect does. This will improve the readability of your actual application logic and you keep the low level generic code abstracted away. But let us start more generically and you can choose how far you want to take this encouragement.
3+
Developing applications is not only about managing state, but also managing side effects. A side effect is typically exampled with an http request or talking to local storage. In Overmind we just call this **effects**.
104

115
## Exposing an existing tool
126

@@ -28,11 +22,54 @@ That was basically it. As you can see we are exposing some low level details lik
2822

2923
## Specific API
3024

25+
It is highly encouraged that you avoid exposing tools with their generic APIs. Rather build your own APIs that are more closely related to your application logic. So for example maybe you have an endpoint for fetching the current user. Create that as an API for your app.
26+
3127
```marksy
3228
h(Example, { name: "guide/runningsideeffects/object" })
3329
```
3430

31+
Now you can see how clean your application logic becomes:
32+
33+
```marksy
34+
h(Example, { name: "guide/runningsideeffects/changestate" })
35+
```
36+
3537
We could also make this effect configurable by defining it as a class instead.
38+
39+
## Initializing effects
40+
41+
It can be a good idea to not allow your side effects to initialize when they are defined. This makes sure that they do not leak into tests or server side rendering. For example if you want to use Firebase. Instead of initializing the Firebase application immediately we rather do it behind an **initialize** method:
42+
43+
```marksy
44+
h(Example, { name: "guide/runningsideeffects/initialize" })
45+
```
46+
47+
We are doing 2 things here:
48+
49+
1. We have created an **initialize** method which we can call from the Overmind **onInitialize** action, which runs when the Overmind instance is created
50+
2. We use an [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) to create a scoped internal variable to be used for that specific effect
51+
52+
Example of initializing the effect:
53+
54+
```marksy
55+
h(Example, { name: "guide/runningsideeffects/oninitialize" })
56+
```
57+
58+
## Lazy effects
59+
60+
You can also lazily load your effects in the **initialize** method. Let us say we wanted to load Firebase and its API on demand, or maybe just split out the code to make our app start faster.
61+
62+
```marksy
63+
h(Example, { name: "guide/runningsideeffects/lazy" })
64+
```
65+
66+
In our initialize we would just have to wait for the initialize to finish before using the API:
67+
68+
69+
```marksy
70+
h(Example, { name: "guide/runningsideeffects/oninitialize_lazy" })
71+
```
72+
3673
## Configurable effect
3774

3875
By defining a class we improve testability, allow using environment variables and even change out the actual request implementation.
@@ -42,5 +79,7 @@ By defining a class we improve testability, allow using environment variables an
4279
h(Example, { name: "guide/runningsideeffects/class" })
4380
```
4481

82+
We export an instance of our **Api** to the application. This allows us to also create instances in isolation for testing purposes, making sure our Api class works as we expect.
83+
4584
## Summary
4685
Adding side effects is easy, but it is worth taking notice that you also get a chance to map an existing tool to something more domain specific. As we did here converting the general **get** method to a **getCurrentUser** method. If you think about it from an application standpoint it is kinda weird that it makes arbitrary requests to a string url. It is better to create an abstraction around it to keep things more maintainable and predictable.

0 commit comments

Comments
 (0)