Skip to content

Commit 9ee0543

Browse files
docs(website): add actions doc and improve frontpage
1 parent 51fefab commit 9ee0543

File tree

13 files changed

+387
-1
lines changed

13 files changed

+387
-1
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const js = [
2+
{
3+
fileName: 'app/actions.js',
4+
code: `
5+
import * as mutations from './mutations'
6+
import * as operations from './operations'
7+
8+
export const initializeApp = action =>
9+
action()
10+
.mutation(mutations.setLoadingUser)
11+
.map(operations.getUser)
12+
.mutation(mutations.setUser)
13+
.mutation(mutations.unsetLoadingUser)
14+
`,
15+
},
16+
]
17+
18+
export const ts = [
19+
{
20+
fileName: 'app/actions.ts',
21+
code: `
22+
import * as mutations from './mutations'
23+
import * as operations from './operations'
24+
25+
export const initializeApp = action =>
26+
action()
27+
.mutation(mutations.setLoadingUser)
28+
.map(operations.getUser)
29+
.mutation(mutations.setUser)
30+
.mutation(mutations.unsetLoadingUser)
31+
`,
32+
},
33+
]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export const js = [
2+
{
3+
fileName: 'app/actions.js',
4+
code: `
5+
import * as operations from './operations'
6+
7+
export const loadApplication = action =>
8+
action()
9+
10+
export const getValidToken = action =>
11+
action()
12+
13+
export const initializeApp = action =>
14+
action()
15+
.when(operations.hasValidToken, {
16+
true: loadApplication(action),
17+
false: getValidToken(action).compose(loadApplication(action))
18+
})
19+
`,
20+
},
21+
]
22+
23+
export const ts = [
24+
{
25+
fileName: 'app/actions.ts',
26+
code: `
27+
import { Action } from './'
28+
import * as operations from './operations'
29+
30+
export const loadApplication: Action = action =>
31+
action()
32+
33+
export const getValidToken: Action = action =>
34+
action()
35+
36+
export const initializeApp: Action = action =>
37+
action()
38+
.when(operations.hasValidToken, {
39+
true: loadApplication(action),
40+
false: getValidToken(action).compose(loadApplication(action))
41+
})
42+
`,
43+
},
44+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const js = [
2+
{
3+
fileName: 'app/actions.js',
4+
code: `
5+
export const initializeApp = action =>
6+
action()
7+
`,
8+
},
9+
]
10+
11+
export const ts = [
12+
{
13+
fileName: 'app/actions.ts',
14+
code: `
15+
export const initializeApp = action =>
16+
action()
17+
`,
18+
},
19+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export const js = [
2+
{
3+
fileName: 'app/actions.js',
4+
code: `
5+
export const initializeApp = action
6+
`,
7+
},
8+
]
9+
10+
export const ts = [
11+
{
12+
fileName: 'app/actions.ts',
13+
code: `
14+
export const initializeApp = action
15+
`,
16+
},
17+
]
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export const js = [
2+
{
3+
fileName: 'app/mutations.js',
4+
code: `
5+
export const setValue = state =>
6+
state.value = 'foo'
7+
8+
export const setValueFromAction = (state, value) =>
9+
state.value2 = value
10+
11+
export const setValueFromState = state =>
12+
state.value3 = state.value
13+
`,
14+
},
15+
{
16+
fileName: 'app/actions.js',
17+
code: `
18+
import * as mutations from './mutations'
19+
20+
export const setValues = action =>
21+
action()
22+
.mutation(mutations.setValue)
23+
.mutation(mutations.setValueFromAction)
24+
.mutation(mutations.setValueFromState)
25+
`,
26+
},
27+
]
28+
29+
export const ts = [
30+
{
31+
fileName: 'app/mutations.js',
32+
code: `
33+
import { Mutation } from './'
34+
35+
export const setValue: Mutation = state =>
36+
state.value = 'foo'
37+
38+
export const setValueFromAction: Mutation<string> = (state, value) =>
39+
state.value2 = value
40+
41+
export const setValueFromState: Mutation = state =>
42+
state.value3 = state.value
43+
`,
44+
},
45+
{
46+
fileName: 'app/actions.js',
47+
code: `
48+
import { Action } from './'
49+
import * as mutations from './mutations'
50+
51+
export const setValues: Action<string> = action =>
52+
action()
53+
.mutation(mutations.setValue)
54+
.mutation(mutations.setValueFromAction)
55+
.mutation(mutations.setValueFromState)
56+
`,
57+
},
58+
]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export const js = [
2+
{
3+
fileName: 'app/actions.js',
4+
code: `
5+
import * as operations from './operations'
6+
7+
export const actionA = action =>
8+
action().map(operations.sayHelloWorld)
9+
10+
export const actionB = action =>
11+
action().map(operations.sayHelloWorldAsync)
12+
13+
export const actionC = action =>
14+
action().map(operations.inputToUpperCase)
15+
`,
16+
},
17+
{
18+
fileName: 'demo.js',
19+
code: `
20+
import app from './app'
21+
22+
app.actions.actionA() // "hello world"
23+
app.actions.actionB() // Promise<"Hello world">
24+
app.actions.actionC("hello world") // "HELLO WORLD"
25+
`,
26+
},
27+
]
28+
29+
export const ts = [
30+
{
31+
fileName: 'app/actions.ts',
32+
code: `
33+
import { Action } from './'
34+
import * as operations from './operations'
35+
36+
export const actionA: Action = action =>
37+
action().map(operations.sayHelloWorld)
38+
39+
export const actionB: Action = action =>
40+
action().map(operations.sayHelloWorldAsync)
41+
42+
export const actionC: Action<string> = action =>
43+
action().map(operations.inputToUpperCase)
44+
`,
45+
},
46+
{
47+
fileName: 'demo.js',
48+
code: `
49+
import app from './app'
50+
51+
app.actions.actionA() // "hello world"
52+
app.actions.actionB() // Promise<"Hello world">
53+
app.actions.actionC("hello world") // "HELLO WORLD"
54+
`,
55+
},
56+
]
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Creating actions
2+
3+
When an event triggers in your application, that being a user interaction, a websocket message etc., you want to run logic that changes the state of the application and/or runs side effects. This logic we express with a concept called **actions**. What is important to understand about actions is that they are an orchestration tool. That means you use actions to compose together different small pieces of logic into a flow of execution. This separation pushes you into a functional approach, giving you several benefits that will be highlighted in this guide.
4+
5+
## Action factory
6+
7+
You define your actions in files named **actions**. From this file you export functions that will receive the action factory function as its only argument.
8+
9+
```marksy
10+
<Example name="guide/creatingactions/factory" />
11+
```
12+
13+
When you call this action factory you create the action. Now... why do we have this factory? Why could you not just do something like this:
14+
15+
```marksy
16+
<Example name="guide/creatingactions/instead" />
17+
```
18+
19+
1. We want actions to be defined as a callback. This allows actions to compose actions from other files, even in circular reference. This is an important flexibility which callbacks enable
20+
2. Since actions can be composed into other actions we want to ensure that every composition is unique. By using an action factory we ensure this
21+
3. Overmind is built with Typescript and this affects the surface API to properly do typing
22+
23+
This might not make too much sense right now, but it will become more clear as we move on in this guide.
24+
25+
## A chaining API
26+
27+
The action returned by the factory has a chaining API. This is the concept that forces you into a functional world. One of the big benefits of this approach is that your code becomes declarative. That means you describe **what** your application is doing in the action chain and then you have small separate functions that actually describes the **how**.
28+
29+
```marksy
30+
<Example name="guide/creatingactions/chain" />
31+
```
32+
33+
Each of the methods on the action we call an **operator**. So **mutation** and **map** are operators.
34+
35+
## Calling an action
36+
37+
When the application is initialized you can start calling the actions. You will typically **connect** your app to components before doing this, but you can also call them directly off the application instance. Let us do that now to show how actions behave.
38+
39+
```marksy
40+
<Example name="guide/creatingactions/trigger" />
41+
```
42+
43+
Here we have three different examples of actions.
44+
45+
1. **actionA** is called without a value and maps to a new value
46+
2. **actionB** is also called without a value, but maps to a promised value
47+
3. **actionC** is called with a value and that value is transformed
48+
49+
What to learn from this is that calling an action is just like calling a plain function that returns its input by default. It is the attaching of operators that gives the action behaviour.
50+
51+
## Changing state
52+
53+
The most common thing you will do is changing the state of the application. You perform a **mutation**. To express this in an action you will use the **mutation** operator. You will define all your mutations in their own files called **mutations**. The reason for that is to be explicit about where mutations actually happens in your application.
54+
55+
```marksy
56+
<Example name="guide/creatingactions/mutations" />
57+
```
58+
59+
As the example shows above there are three ways to change the state of the application:
60+
61+
1. Using an explicit value
62+
2. Using a value passed to the action
63+
3. Using a value from the state tree
64+
65+
Note that when you use an existing value from the state tree that value has to be a "plain value", meaning that it can not be an existing array or object. It has to be a string, number, boolean or null. The reason is that in a single state tree you should not have the same object or array in multiple parts of the tree. That would break the tracking of changes. Do not worry though, this is very uncommon to do and if you do it, an error is thrown.
66+
67+
## Composing actions
68+
69+
A powerful concept in Overmind is that you can compose actions together. There are several operators that supports this. We will look at one of those operators here, the **when** operator.
70+
71+
```marksy
72+
<Example name="guide/creatingactions/composing" />
73+
```
74+
75+
Since each action is defined with a function we call that function and pass it the action factory. This ensures that every composition is unique. Also since each function is defined as a function we can now import actions from anywhere else, even circular imports.
76+
77+
## Summary
78+
79+
This guide gave you some insight into what actions are about. There are still a lot of operators to look into and you can learn more about them in the [side effects]() or go to the [API](http://localhost:4000/api/action) section for actions.
80+

packages/overmind-website/src/components/App/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class App extends React.Component<{}, State> {
6868

6969
page.redirect('/api', '/api/action')
7070
}
71+
componentDidUpdate(_, prevState) {
72+
if (prevState.currentPath !== this.state.currentPath) {
73+
document.querySelector('#overmind-app').scrollTop = 0
74+
}
75+
}
7176
componentDidMount() {
7277
const el = document.querySelector('#overmind-app') as HTMLElement
7378

packages/overmind-website/src/components/Doc/elements.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ export const Content = styled.div`
1111
font-size: 18px;
1212
color: ${({ theme }) => theme.color.black};
1313
14+
ol {
15+
list-style: none;
16+
counter-reset: li;
17+
}
18+
ol li {
19+
counter-increment: li;
20+
}
21+
ol li::before {
22+
content: counter(li);
23+
color: ${({ theme }) => theme.color.primary};
24+
font-weight: bold;
25+
display: inline-block;
26+
width: 1em;
27+
margin-left: -1em;
28+
}
29+
ol,
30+
ul {
31+
margin-top: ${({ theme }) => theme.padding.large};
32+
margin-bottom: ${({ theme }) => theme.padding.large};
33+
}
34+
li {
35+
margin-bottom: 15px;
36+
}
1437
> p {
1538
line-height: 26px;
1639
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,29 @@
11
import styled from '../../styled-components'
2+
3+
export const QuickstartWrapper = styled.div`
4+
height: 75px;
5+
position: fixed;
6+
bottom: 0;
7+
left: 0;
8+
width: 100vw;
9+
display: flex;
10+
`
11+
12+
export const Quickstart = styled.a`
13+
cursor: pointer;
14+
border: 2px solid ${({ theme }) => theme.color.dark};
15+
background-color: ${({ theme }) =>
16+
theme.color.lighten(theme.color.dark, -0.5)};
17+
display: flex;
18+
align-items: center;
19+
justify-content: center;
20+
text-decoration: none;
21+
flex: 1;
22+
> *:first-child {
23+
margin-right: 15px;
24+
}
25+
color: ${({ theme }) => theme.color.fade(theme.color.white, 0.25)};
26+
:hover {
27+
color: ${({ theme }) => theme.color.white};
28+
}
29+
`

0 commit comments

Comments
 (0)