Skip to content

Commit 74eb13c

Browse files
docs(website): add functional docs
1 parent 7f70939 commit 74eb13c

File tree

9 files changed

+269
-9
lines changed

9 files changed

+269
-9
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/actions.ts',
6+
code: `
7+
import { Operator, Action, action } from 'overmind'
8+
9+
export const normalAction: Action = ({ value, state }) => {
10+
11+
}
12+
13+
export const functionalAction: Operator = action(({ value, state }) => {
14+
15+
})
16+
`,
17+
},
18+
]
19+
: [
20+
{
21+
fileName: 'overmind/actions.js',
22+
code: `
23+
import { action } from 'overmind'
24+
25+
export const normalAction = ({ value, state }) => {
26+
27+
}
28+
29+
export const functionalAction = action(({ value, state }) => {
30+
31+
})
32+
`,
33+
},
34+
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/operators.ts',
6+
code: `
7+
import { Operator, map, filter } from 'overmind'
8+
9+
const getEventTargetValue: Operator<Event, string> =
10+
map(({ value }) => value.currentTarget.value)
11+
12+
const lengthGreaterThan: (length: number) => Operator<string> =
13+
(length) => filter(({ value }) => value.length > length)
14+
15+
`,
16+
},
17+
{
18+
fileName: 'overmind/actions.ts',
19+
code: `
20+
import { Operator, pipe, debounce, action } from 'overmind'
21+
22+
export const search: Operator<Event> = pipe(
23+
getEventTargetValue,
24+
lengthGreaterThan(2),
25+
debounce(200),
26+
action(async ({ value: query, state, api }) => {
27+
state.isSearching = true
28+
state.searchResult = await api.search(query)
29+
state.isSearching = false
30+
})
31+
)
32+
`,
33+
},
34+
]
35+
: [
36+
{
37+
fileName: 'overmind/operators.js',
38+
code: `
39+
import { map, filter } from 'overmind'
40+
41+
export const getEventTargetValue = map(({ value }) => value.currentTarget.value)
42+
43+
export const lengthGreaterThan = (length) => filter(({ value }) => value.length > length)
44+
`,
45+
},
46+
{
47+
fileName: 'overmind/actions.js',
48+
code: `
49+
import { pipe, debounce, action } from 'overmind'
50+
import { getEventTargetValue, lengthGreaterThan } from './operators'
51+
52+
export const search = pipe(
53+
getEventTargetValue,
54+
lengthGreaterThan(2),
55+
debounce(200),
56+
action(async ({ value: query, state, api }) => {
57+
state.isSearching = true
58+
state.searchResult = await api.search(query)
59+
state.isSearching = false
60+
})
61+
)
62+
`,
63+
},
64+
]
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/actions.ts',
6+
code: `
7+
import { Action } from 'overmind'
8+
9+
let debounce
10+
export const search: Action<Event> = ({ value: event, state, api }) => {
11+
state.query = event.currentTarget.value
12+
13+
if (query.length < 3) return
14+
15+
if (debounce) clearTimeout(debounce)
16+
17+
debounce = setTimeout(async () => {
18+
state.isSearching = true
19+
state.searchResult = await api.search(state.query)
20+
state.isSearching = false
21+
22+
debounce = null
23+
}, 200)
24+
}
25+
`,
26+
},
27+
]
28+
: [
29+
{
30+
fileName: 'overmind/state.js',
31+
code: `
32+
let debounce
33+
export const search = ({ value: event, state, api }) => {
34+
state.query = event.currentTarget.value
35+
36+
if (query.length < 3) return
37+
38+
if (debounce) clearTimeout(debounce)
39+
40+
debounce = setTimeout(async () => {
41+
state.isSearching = true
42+
state.searchResult = await api.search(state.query)
43+
state.isSearching = false
44+
45+
debounce = null
46+
}, 200)
47+
}
48+
`,
49+
},
50+
]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export default (ts) =>
2+
ts
3+
? [
4+
{
5+
fileName: 'overmind/actions.ts',
6+
code: `
7+
import { Operator, pipe } from 'overmind'
8+
9+
export const pipeA: Operator = pipe(
10+
operatorA,
11+
operatorB
12+
)
13+
14+
export const pipeB: Operator = pipe(
15+
pipeA,
16+
operatorC,
17+
operatorD
18+
)
19+
`,
20+
},
21+
]
22+
: [
23+
{
24+
fileName: 'overmind/actions.js',
25+
code: `
26+
import { pipe } from 'overmind'
27+
28+
export const pipeA = pipe(
29+
operatorA,
30+
operatorB
31+
)
32+
33+
export const pipeB = pipe(
34+
pipeA,
35+
operatorC,
36+
operatorD
37+
)
38+
`,
39+
},
40+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default () => [
2+
{
3+
code: `
4+
import { Operator, action } from 'overmind'
5+
6+
export const doThis: Operator<string, number> = action(() => {})
7+
`,
8+
},
9+
]

packages/overmind-website/examples/guide/typescript/explicit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
1313
const config = {}
1414
15-
export type Config = TConfig<{
15+
type Config = TConfig<{
1616
state: typeof config["state"]
1717
actions: typeof config["actions"]
1818
effects: typeof config["effects"]
@@ -22,7 +22,7 @@ export type OnInitialize = TOnInitialize<Config>
2222
2323
export type Action<Input = void> = TAction<Config, Input>
2424
25-
export type Operator<Input, Output> = TOperator<Config, Input, Output>
25+
export type Operator<Input = void, Output = Input> = TOperator<Config, Input, Output>
2626
2727
export type Derive<Parent extends TStateObject, Output> = TDerive<Config, Parent, Output>
2828
`,

packages/overmind-website/examples/guide/typescript/explicit_operators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Config, Operator } from '../'
77
const toUpperCase = map<string, string, Config>(...)
88
const doSomething = run<string, Config>(...)
99
10-
export const doThis: Operator<string, string> = pipe(
10+
export const doThis: Operator<string> = pipe(
1111
toUpperCase,
1212
doSomething
1313
)

packages/overmind-website/guides/intermediate/03_typescript.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,4 @@ You can also explicitly type your application. This gives more flexibility.
2121

2222
```marksy
2323
h(Example, { name: "guide/typescript/explicit.ts" })
24-
```
25-
26-
If you use the **operators** api you just pass the config as the last type argument.
27-
28-
```marksy
29-
h(Example, { name: "guide/typescript/explicit_operators.ts" })
3024
```
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Going functional
2+
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.
4+
5+
Let us look at a concrete example of how messy an imperative approach would be compared to a functional approach.
6+
7+
```marksy
8+
h(Example, { name: "guide/goingfunctional/messy" })
9+
```
10+
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.
12+
13+
If we were to do this in a functional style it would look more like this:
14+
15+
```marksy
16+
h(Example, { name: "guide/goingfunctional/clean" })
17+
```
18+
19+
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".
20+
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.
22+
23+
## Converting actions to functional actions
24+
25+
To get going with functional code you can simply convert any existing action by using the **action** operator.
26+
27+
```marksy
28+
h(Example, { name: "guide/goingfunctional/actionoperator" })
29+
```
30+
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.
32+
33+
## Piping
34+
35+
To compose the different operators together you typically use **pipe**. You can also compose pipes into pipes, it is just an operator like the rest.
36+
37+
```marksy
38+
h(Example, { name: "guide/goingfunctional/pipe" })
39+
```
40+
41+
There are several operators available and you can quite easily create new operators from scratch. They er built with the [op-op spec](https://github.com/christianalfoni/op-op-spec). A specification designed specifically to lower the threshold of moving into the functional world.
42+
43+
## Typescript
44+
45+
All operators have the same type.
46+
47+
`TOperator<Config, Input, Output>`
48+
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.
50+
51+
Just like the **Action** type, the **Operator** type does not need any arguments. That means it expects no value to be passed in.
52+
53+
`Operator`
54+
55+
If you do define an *input*, that also becomes the *output*.
56+
57+
`Operator<string>`
58+
59+
Or you can of course define both.
60+
61+
`Operator<string, number>`
62+
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.
64+
65+
```marksy
66+
h(Example, { name: "guide/goingfunctional/wrongoperator" })
67+
```
68+
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 :-)

0 commit comments

Comments
 (0)