Skip to content

Commit fbfa173

Browse files
docs(website): improved some docs and added view helper
1 parent 75e3f2b commit fbfa173

File tree

13 files changed

+394
-22
lines changed

13 files changed

+394
-22
lines changed

packages/overmind-website/examples/guide/connectingcomponents/object_2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ import { connect, Connect } from '../overmind'
8787
8888
type Props = {
8989
item: { title: string }
90-
}
90+
} & Connect
9191
92-
const Item: React.SFC<Connect & Props> = ({ item }) => (
92+
const Item: React.SFC<Props> = ({ item }) => (
9393
<li>{item.title}</li>
9494
)
9595
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
const javascript = {
2+
react: [
3+
{
4+
fileName: 'overmind/actions.js',
5+
code: `
6+
import { parallel, action } from 'overmind'
7+
8+
export const grabData = parallel(
9+
action(async ({ state, api }) => {
10+
state.posts = await api.getPosts()
11+
}),
12+
action(async ({ state, api }) => {
13+
state.users = await api.getUsers()
14+
})
15+
)
16+
`,
17+
},
18+
{
19+
fileName: 'components/MyComponent.jsx',
20+
target: 'jsx',
21+
code: `
22+
import React from 'react'
23+
import { connect } from '../overmind'
24+
25+
const MyComponent = ({ overmind }) => (
26+
<button onClick={overmind.actions.grabData}>
27+
Grab some data
28+
</button>
29+
)
30+
31+
export default connect(MyComponent)
32+
`,
33+
},
34+
],
35+
vue: [
36+
{
37+
fileName: 'overmind/actions.js',
38+
code: `
39+
import { parallel, action } from 'overmind'
40+
41+
export const grabData = parallel(
42+
action(async ({ state, api }) => {
43+
state.posts = await api.getPosts()
44+
}),
45+
action(async ({ state, api }) => {
46+
state.users = await api.getUsers()
47+
})
48+
)
49+
`,
50+
},
51+
{
52+
fileName: 'components/MyComponent.vue (template)',
53+
target: 'markup',
54+
code: `
55+
<button on:click="overmind.actions.grabData()">
56+
Grab some data
57+
</button>
58+
`,
59+
},
60+
{
61+
fileName: 'components/MyComponent.vue (script)',
62+
code: `
63+
import { connect } from '../overmind'
64+
65+
export default connect({})
66+
`,
67+
},
68+
],
69+
}
70+
71+
const typescript = {
72+
react: [
73+
{
74+
fileName: 'overmind/actions.js',
75+
code: `
76+
import { Operator, parallel, action } from 'overmind'
77+
78+
export const grabData: Operator = parallel(
79+
action(async ({ state, api }) => {
80+
state.posts = await api.getPosts()
81+
}),
82+
action(async ({ state, api }) => {
83+
state.users = await api.getUsers()
84+
})
85+
)
86+
`,
87+
},
88+
{
89+
fileName: 'components/MyComponent.tsx',
90+
code: `
91+
import * as React from 'react'
92+
import { connect, Connect } from '../overmind'
93+
94+
type Props = Connect
95+
96+
const MyComponent: React.SFC<Props> = ({ overmind }) => (
97+
<button onClick={overmind.actions.grabData}>
98+
Grab some data
99+
</button>
100+
)
101+
102+
export default connect(MyComponent)
103+
`,
104+
},
105+
],
106+
vue: [
107+
{
108+
fileName: 'overmind/actions.ts',
109+
code: `
110+
import { Operator, parallel, action } from 'overmind'
111+
112+
export const grabData: Operator = parallel(
113+
action(async ({ state, api }) => {
114+
state.posts = await api.getPosts()
115+
}),
116+
action(async ({ state, api }) => {
117+
state.users = await api.getUsers()
118+
})
119+
)
120+
`,
121+
},
122+
{
123+
fileName: 'components/MyComponent.vue (template)',
124+
target: 'markup',
125+
code: `
126+
<button on:click="overmind.actions.grabData()">
127+
Grab some data
128+
</button>
129+
`,
130+
},
131+
{
132+
fileName: 'components/MyComponent.vue (script)',
133+
code: `
134+
import { connect } from '../overmind'
135+
136+
export default connect({})
137+
`,
138+
},
139+
],
140+
angular: [
141+
{
142+
fileName: 'overmind/actions.js',
143+
code: `
144+
import { Operator, parallel, action } from 'overmind'
145+
146+
export const grabData: Operator = parallel(
147+
action(async ({ state, api }) => {
148+
state.posts = await api.getPosts()
149+
}),
150+
action(async ({ state, api }) => {
151+
state.users = await api.getUsers()
152+
})
153+
)
154+
`,
155+
},
156+
{
157+
fileName: 'components/grabdata.component.ts',
158+
code: `
159+
import { Component,Input } from '@angular/core';
160+
import { connect } from '../overmind'
161+
162+
@Component({
163+
selector: 'grab-data',
164+
template: \`
165+
<button (click)="overmind.actions.grabData()">
166+
Grab some data
167+
</button>
168+
\`
169+
})
170+
@connect()
171+
export class GrabData {}
172+
`,
173+
},
174+
],
175+
}
176+
177+
export default (ts, view) => (ts ? typescript[view] : javascript[view])
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default () => [
2+
{
3+
fileName: 'overmind/operators.ts',
4+
code: `
5+
import { Operator, action } from 'overmind'
6+
7+
export const doSomeStateChange: Operator =
8+
action(({ state }) => {
9+
state.foo = 'bar'
10+
})
11+
`,
12+
},
13+
{
14+
fileName: 'overmind/actions.ts',
15+
code: `
16+
import { Operator, pipe, action } from 'overmind'
17+
import { doSomeStateChange } from './operators'
18+
19+
export const setInput: Operator<string> = pipe(
20+
doSomeStateChange,
21+
action(({ value: input, state }) => {
22+
state.input = input
23+
})
24+
)
25+
`,
26+
},
27+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export default () => [
2+
{
3+
code: `
4+
import { Operator, action, filter, map } from 'overmind'
5+
6+
// You do not need to define any types, which means it defaults
7+
// its input and output to "void"
8+
export const changeSomeState: Operator = action(({ state }) => {
9+
state.foo = 'bar'
10+
})
11+
12+
// The second type argument is not set, but will default to "User"
13+
// The output is the same as the input
14+
export const filterAwesomeUser: Operator<User> =
15+
filter(({ value: user }) => user.isAwesome)
16+
17+
// "map" produces a new output so we define that as the second
18+
// type argument
19+
export const getEventTargetValue: Operator<Event, string> =
20+
map(({ value: event }) => event.currentTarget.value)
21+
`,
22+
},
23+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export default () => [
2+
{
3+
fileName: 'overmind/operators.ts',
4+
code: `
5+
import { Operator, filter } from 'overmind'
6+
7+
export const filterAwesomeUser: Operator<{ isAwesome: boolean }> =
8+
filter(({ value: somethingAwesome }) => somethingAwesome.isAwesome)
9+
`,
10+
},
11+
{
12+
fileName: 'overmind/actions.ts',
13+
code: `
14+
import { Operator, pipe, action } from 'overmind'
15+
import { filterAwesomeUser } from './operators'
16+
import { User } from './state'
17+
18+
export const clickedUser: Operator<User> = pipe(
19+
filterAwesomeUser,
20+
action(({ value: user, state }) => {
21+
state.awesomeUsersClickedCount++
22+
})
23+
)
24+
`,
25+
},
26+
]

packages/overmind-website/examples/guide/managinglists/render2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ import Post from './Post'
107107
const Posts: React.SFC<Connect> = ({ overmind }) => (
108108
<ul>
109109
{overmind.state.postsList.map(post =>
110-
<Item key={post.id} post={post} />
110+
<Post key={post.id} post={post} />
111111
)}
112112
</ul>
113113
)
@@ -123,7 +123,7 @@ export default connect(Posts)
123123
code: `
124124
import { Component,Input } from '@angular/core';
125125
import { connect } from '../overmind'
126-
import { Item } from '../overmind/state'
126+
import { Post } from '../overmind/state'
127127
128128
@Component({
129129
selector: 'app-post',
Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Going functional
22

3-
You get very building your application with straight forward imperative actions. This is typically how we learn programming and is arguably close to how we think about the world. But this approach lacks a good structured way to compose multiple smaller pieces together. Reusing existing logic in multiple contexts. As the complexity of your application increases you will find benefits doing some of your logic, or maybe all your logic, in a functional style.
3+
You get very far building your application with straight forward imperative actions. This is typically how we learn programming and is arguably close to how we think about the world. But this approach lacks a good structured way to compose multiple smaller pieces together. Reusing existing logic in multiple contexts. As the complexity of your application increases you will find benefits doing some of your logic, or maybe all your logic, in a functional style.
44

55
Let us look at a concrete example of how messy an imperative approach would be compared to a functional approach.
66

77
```marksy
88
h(Example, { name: "guide/goingfunctional/messy" })
99
```
1010

11-
What we see here is an action trying to express doing a search. We only want to search when the length of the query is more than 2 and we only want to trigger the search when the user has not pressed any keys for 200 milliseconds.
11+
What we see here is an action trying to express doing a search. We only want to search when the length of the query is more than 2 and we only want to trigger the search when the user has not changed the query for 200 milliseconds.
1212

1313
If we were to do this in a functional style it would look more like this:
1414

@@ -18,7 +18,7 @@ h(Example, { name: "guide/goingfunctional/clean" })
1818

1919
Now we have created a couple of custom operators that we can reuse in other compositions. In addition we have made our code declarative. Instead of showing implementation details we rather "tell the story of the code".
2020

21-
The great thing about the operator API is that you can use it to any extent you want, even drop it if the complexity of your app does not reach a level where it makes sense.
21+
The great thing about the operator API is that you can think of it is "upgrading" from an action, cause you can use actions as an operator as well. You can use operators to any extent you want, even drop it completely if the complexity of your app does not reach a level where it makes sense to use them.
2222

2323
## Converting actions to functional actions
2424

@@ -28,7 +28,11 @@ To get going with functional code you can simply convert any existing action by
2828
h(Example, { name: "guide/goingfunctional/actionoperator" })
2929
```
3030

31-
This makes your action a composable piece to be used with other operators. But actually **all** operators can be called as an action, not only the action operator. When you attach an operator to your actions configuration, you can call them from components.
31+
This makes your action a composable piece to be used with other operators. Even though we used an operator you can still call this as normal from a component:
32+
33+
```marksy
34+
h(Example, { name: "guide/goingfunctional/callactionoperator" })
35+
```
3236

3337
## Piping
3438

@@ -46,24 +50,42 @@ All operators have the same type.
4650

4751
`TOperator<Config, Input, Output>`
4852

49-
That means all operators has an input and an output. For most of the operators the output is the same as input, though with others, like **map**, it produces a new output. When you use the consumable type, either directly from Overmind or with explicit typing, there are some defaults.
53+
That means all operators has an input and an output. For most of the operators the output is the same as input, though with others, like **map**, it produces a new output.
54+
55+
```marksy
56+
h(Example, { name: "guide/goingfunctional/operatorinputsandoutputs" })
57+
```
58+
59+
Now what is important to understand is that the Operator type will yell at you if you use it incorrectly with an actual operator. For example if you define a different output than input for an action operator. That is because an action operator is typed to pass its input as output.
60+
61+
```marksy
62+
h(Example, { name: "guide/goingfunctional/wrongoperator" })
63+
```
5064

51-
Just like the **Action** type, the **Operator** type does not need any arguments. That means it expects no value to be passed in.
65+
You will also get yelled at by Typescript if you compose together operators that does not match outputs with inputs. But yeah, that is why we use it :-)
5266

53-
`Operator`
67+
### Caveats
68+
There are two **limitations** to the operators type system which we are still trying to figure out:
5469

55-
If you do define an *input*, that also becomes the *output*.
70+
#### 1. Partial input type
5671

57-
`Operator<string>`
72+
For example:
5873

59-
Or you can of course define both.
74+
```marksy
75+
h(Example, { name: "guide/goingfunctional/operatorpartial" })
76+
```
6077

61-
`Operator<string, number>`
78+
There is no way to express in Typescript that you should uphold this partial typing of **filterAwesomeUser** and still pass the inferred input, **User**, as the output. This will give a typing error.
6279

63-
Now what is important to understand is that the Operator type will yell at you if you use it incorrectly with an actual operator. For example if you define a different output than input for an action operator. That is because an action operator is typed to pass its input as output.
80+
#### 2. Infer input
81+
82+
You might create an operator that does not care about its input. For example:
6483

6584
```marksy
66-
h(Example, { name: "guide/goingfunctional/wrongoperator" })
85+
h(Example, { name: "guide/goingfunctional/operatorinfer" })
6786
```
6887

69-
You will also get yelled at by Typescript if you compose together operators that does not match outputs with inputs. But yeah, that is why we use it :-)
88+
If you were to compose this action into a **pipe** you would get an error, cause this operator now expects a **void** type. You would have to inline the **doSomeStateChange** operator into the pipe in question to infer the correct input.
89+
90+
#### Summary
91+
Typescript is not functional, it is object oriented and it is very difficult to make it work with this kind of APIs. We have high hopes though cause Typescript is evolving rapidly and Microsoft is dedicated to make this an awesome type system for JavaScript. If you got mad Typescript skills, please contact us to iterate on the type system and make this stuff work :-)

0 commit comments

Comments
 (0)