Skip to content

Commit 40e1a9c

Browse files
christianalfonigitbook-bot
authored andcommitted
GitBook: [master] 11 pages modified
1 parent 7d7d558 commit 40e1a9c

File tree

11 files changed

+318
-3
lines changed

11 files changed

+318
-3
lines changed

README.md

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,63 @@
1-
# overmind
2-
Repo for proxy based Cerebral implementation (working-title)
1+
# Overmind
2+
3+
### Why Overmind?
4+
5+
Instead of writing components that isolates state:
6+
7+
{% code-tabs %}
8+
{% code-tabs-item title="react.js" %}
9+
```javascript
10+
import React from 'react'
11+
12+
class InputComponent extends React.Component {
13+
state = {
14+
value: ''
15+
}
16+
changeValue = (event) => {
17+
this.setState({
18+
value: event.target.value
19+
})
20+
}
21+
render () {
22+
return <input value={this.state.value} onChange={this.changeValue} />
23+
}
24+
}
25+
```
26+
{% endcode-tabs-item %}
27+
{% endcode-tabs %}
28+
29+
You keep your state and logic to change that state in Overmind:
30+
31+
{% code-tabs %}
32+
{% code-tabs-item title="react.js" %}
33+
```javascript
34+
import App from 'overmind/react'
35+
36+
const app = new App({
37+
state: {
38+
value: ''
39+
},
40+
actions: action => ({
41+
changeValue: action()
42+
.map((event) => event.target.value)
43+
.mutation((state, value) => state.value = value)
44+
})
45+
})
46+
47+
const InputComponent = app.connect(
48+
function InputComponent ({ appState, actions }) {
49+
return <input value={appState.value} onChange={actions.changeValue} />
50+
}
51+
)
52+
```
53+
{% endcode-tabs-item %}
54+
{% endcode-tabs %}
55+
56+
There are many tools, like Redux, Mobx, Vuex, Hyperapp etc., that also moves state and logic outside your components. Where Overmind differs is:
57+
58+
* **Automatic render.** When you connect your app to a component the component will automatically and optimally rerender based on the state that is being used inside that component
59+
* **Safe mutations**. There is only one place in Overmind you can change the state of your application and that is inside the mutation operator. Any outside mutations throws errors
60+
* **Next level devtools.** The Overmind devtools gives you insight into everything that is happening inside your application. That being what state connected components are watching, actions being run, mutations performed and even side effects run
61+
* **Functional actions.** Instead of expressing logic as functions or methods, Overmind exposes an action chaining API. This forces you into a functional approach. Functional code encourages writing many small and focused functions that does one thing. This keeps your application more testable, maintainable and composable
62+
* **Typescript for the win**. You do not have to use Typescript with Overmind, but when you do you will get full type safety in action chains and components
363

4-
Join the implementation at: [Overmind Implementation Specification](https://cerebral.gitbook.io/overmind/)

SUMMARY.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Table of contents
2+
3+
* [Overmind](README.md)
4+
* [Install](install.md)
5+
* [Create an app](create-an-app.md)
6+
* [Connect to a view](connect-to-a-view/README.md)
7+
* [React](connect-to-a-view/react.md)
8+
* [Vue](connect-to-a-view/vue.md)
9+
* [Other views](connect-to-a-view/other-views.md)
10+
* [Devtools](devtools.md)
11+
* [Providers](untitled.md)
12+
* [Organizing apps](organizing-apps.md)
13+

connect-to-a-view/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Connect to a view
2+

connect-to-a-view/other-views.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Other views
2+
3+
The default instance of Overmind exposes tools to track access to state. This allows you to integrate Overmind with any view layer you want. Overmind has several preset view implementations like **React**, **VueJS, Preact** etc.
4+
5+
```typescript
6+
import App from 'overmind'
7+
8+
const {
9+
state,
10+
actions,
11+
trackState,
12+
clearTrackState,
13+
addMutationListener
14+
} = new App({
15+
state: {
16+
title: 'Hello from Overmind'
17+
},
18+
actions: (action) => ({
19+
changeTitle: action()
20+
.map(event => event.target.value)
21+
.mutate((state, value) => state.title = value)
22+
})
23+
})
24+
```
25+
26+
#### state
27+
28+
This is the state of your application.
29+
30+
#### actions
31+
32+
The actions defined. These are just plain functions you call with a value:
33+
34+
```typescript
35+
document.querySelector('#input').addEventListener('input', actions.changeTitle)
36+
```
37+
38+
#### trackState / clearTrackState
39+
40+
This function allows you to track when state is accessed. Typically you would use this in combination with lifecycle hooks of components to track what the component accesses on render:
41+
42+
```typescript
43+
const trackId = trackState('ComponentName')
44+
// Logic that renders a component
45+
const paths = clearTrackState(trackId)
46+
47+
paths // Set { 'path.to.some.state', 'path.to.some.other.state' }
48+
```
49+
50+
This information is automatically passed to the Overmind devtools.
51+
52+
**addMutationListener**
53+
54+
This function is used with the tracked paths. You typically create the listener on the first render and update it on subsequent renders:
55+
56+
```typescript
57+
const listener = addMutationListener(paths, () => {
58+
// This callback is called when the paths are mutated
59+
// Typically here you trigger a new render of the component
60+
// and update the paths of the listener with the new tracked paths
61+
})
62+
63+
listener.update(newPaths) // Update the listener with new paths
64+
listener.dispose() // Dispose the listener, typically when component unmounts
65+
```
66+
67+
Look at the Overmind implementations of the different views to see how this is used in practice.
68+

connect-to-a-view/react.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# React
2+

connect-to-a-view/vue.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Vue
2+

create-an-app.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Create an app
2+

devtools.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Devtools
2+

install.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Install
2+

organizing-apps.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Organizing apps
2+
3+
This example shows a structured application using two modules. The **compose** function allows you to namespace your modules. In this example we are not taking any shortcuts. We explicitly split up and type all functions, components etc. By default this typing would be inferred when inlining the functions and components, but we are taking full advantage of the functional approach which makes the application highly testable, predictable and composable. The usage of two modules here is artificial, but important to show you how modules has access to each other.
4+
5+
{% code-tabs %}
6+
{% code-tabs-item title="index.tsx" %}
7+
```typescript
8+
import React from 'react'
9+
import { render } from 'react-dom'
10+
import App from './components/App'
11+
12+
render(<App />, document.querySelector('#app'))
13+
```
14+
{% endcode-tabs-item %}
15+
16+
{% code-tabs-item title="overmind.ts" %}
17+
```typescript
18+
import Overmind, { IContext, IAction, IConnect, compose } from 'overmind/react'
19+
import * as main from './modules/main'
20+
import * as items from './modules/items'
21+
22+
export type AppState = {
23+
main: main.State,
24+
items: items.State
25+
}
26+
27+
export type Context = IContext<AppState>
28+
29+
export type Action = IAction<Context>
30+
31+
const app = new Overmind(compose({
32+
main,
33+
items
34+
}))
35+
36+
export type Connect = IConnect<typeof app.state, typeof app.actions>
37+
38+
export const connect = app.connect
39+
40+
```
41+
{% endcode-tabs-item %}
42+
43+
{% code-tabs-item title="modules/main/index.ts" %}
44+
```typescript
45+
import { AppState, Action } from '../../overmind'
46+
47+
/*
48+
STATE
49+
*/
50+
export type State = {
51+
newItemValue: string
52+
}
53+
54+
export const state: State = {
55+
newItemValue: ''
56+
}
57+
58+
/*
59+
MUTATIONS
60+
*/
61+
export const setNewItemValue = (state: AppState, value: string) => state.main.newItemValue = value
62+
63+
/*
64+
HELPERS
65+
*/
66+
export const getEventValue = (event: React.ChangeEvent) => event.target.value
67+
68+
/*
69+
ACTIONS
70+
*/
71+
export const actions = (action: Action) => ({
72+
changeNewItemValue: action<React.ChangeEvent>()
73+
.map(getEventValue)
74+
.mutation(setNewItemValue)
75+
})
76+
```
77+
{% endcode-tabs-item %}
78+
79+
{% code-tabs-item title="modules/items/index.ts" %}
80+
```typescript
81+
import { AppState, Context, Action } from '../../overmind'
82+
83+
/*
84+
STATE
85+
*/
86+
export type Item = {
87+
title: string
88+
}
89+
90+
export type State = {
91+
items: Item[]
92+
}
93+
94+
export const state: State = {
95+
items: []
96+
}
97+
98+
/*
99+
MUTATIONS
100+
*/
101+
export const addNewItem = (state: AppState, item: Item) => state.items.list.push(item)
102+
103+
export const resetNewItemValue = (state: AppState) => state.main.newItemValue = ''
104+
105+
/*
106+
HELPERS
107+
*/
108+
export const createItem = (_, { state }: Context) => ({
109+
title: state.main.newItemValue
110+
})
111+
112+
/*
113+
ACTIONS
114+
*/
115+
export const actions = (action: Action) => ({
116+
addNewItem: action()
117+
.map(createItem)
118+
.mutation(addNewItem)
119+
.mutation(resetNewItemValue)
120+
})
121+
```
122+
{% endcode-tabs-item %}
123+
124+
{% code-tabs-item title="components/App/index.tsx" %}
125+
```typescript
126+
import React from 'react'
127+
importconnect, Connect } from '../../overmind'
128+
129+
const App: React.SFC<Connect> = ({ appState, actions }) => (
130+
<div>
131+
<form onSubmit={event => {
132+
event.preventDefault()
133+
actions.items.addNewItem()
134+
}}>
135+
<input
136+
onChange={actions.changeNewItemValue}
137+
value={appState.main.newItemValue}
138+
/>
139+
</form>
140+
<ul>
141+
{appState.items.list.map(item => (
142+
<li>{item.title}</li>
143+
))}
144+
</ul>
145+
</div>
146+
)
147+
148+
export default connect(App)
149+
```
150+
{% endcode-tabs-item %}
151+
{% endcode-tabs %}
152+
153+
What to take notice of:
154+
155+
* We are not interfering in any way with the initial rendering of the app, meaning you just connect state where you need it
156+
* By default Overmind takes a single module with state, actions etc., but **compose** allows us to merge multiple modules together, giving them a namespace \("main" and "items" in this example\)
157+
* We separate mutations and other side effects. This makes it absolutely clear where mutations are performed and only the **mutation** operator is allowed to perform these mutations.
158+
* The actions are now just plain functions taking any payload. When the action is typed it requires a value, when it is not typed, it does not require a value \(this is actually very difficult to do in TypeScript\)
159+
160+
161+

0 commit comments

Comments
 (0)