Skip to content

Commit eb53491

Browse files
Merge pull request cerebral#9 from cerebral/derived
Derived
2 parents d84ceb1 + dbaa501 commit eb53491

File tree

12 files changed

+230
-96
lines changed

12 files changed

+230
-96
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { derived } from 'overmind'
2+
import { State, Component } from './state'
3+
4+
export const currentApp = derived(
5+
(state: State) => state.apps[state.currentPort]
6+
)
7+
8+
export const componentsMounted = derived((state: State) =>
9+
Object.keys(state.currentApp.components).reduce(
10+
(aggr, key) => {
11+
if (state.currentApp.components[key].isMounted) {
12+
return aggr.concat(state.currentApp.components[key])
13+
}
14+
15+
return aggr
16+
},
17+
[] as Component[]
18+
)
19+
)
20+
21+
export const componentsUpdateCount = derived((state: State) =>
22+
state.componentsMounted.reduce(
23+
(aggr, component) => aggr + component.updateCount,
24+
0
25+
)
26+
)
27+
28+
export const componentsStatePathCount = derived((state: State) =>
29+
state.componentsMounted.reduce(
30+
(aggr, component) => aggr + component.paths.length,
31+
0
32+
)
33+
)

packages/node_modules/overmind-devtools/src/client/app/state.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as derived from './derived'
2+
13
export type Mutation = {
24
args: any[]
35
method: string
@@ -59,6 +61,10 @@ export type State = {
5961
newPortValue: string
6062
currentTab: Tab
6163
expandedStatePaths: string[]
64+
currentApp: App
65+
componentsMounted: Component[]
66+
componentsUpdateCount: number
67+
componentsStatePathCount: number
6268
}
6369

6470
const state: State = {
@@ -70,6 +76,10 @@ const state: State = {
7076
newPortValue: '',
7177
currentTab: Tab.State,
7278
expandedStatePaths: [''],
79+
currentApp: derived.currentApp,
80+
componentsMounted: derived.componentsMounted,
81+
componentsUpdateCount: derived.componentsUpdateCount,
82+
componentsStatePathCount: derived.componentsStatePathCount,
7383
}
7484

7585
export default state

packages/node_modules/overmind-devtools/src/client/components/Components/index.tsx

Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,52 @@ import Table, { Row, Cell } from '../common/Table'
55
import Pill from '../common/Pill'
66
import { Wrapper, Panels, Panel } from './elements'
77

8-
const Components: React.SFC<Connect> = ({ appState }) => {
9-
const components = appState.apps[appState.currentPort].components
10-
const keys = Object.keys(components)
11-
const mountedKeys = keys.filter((key) => components[key].isMounted)
12-
const updatesCount = keys.reduce(
13-
(aggr, key) => aggr + components[key].updateCount,
14-
0
15-
)
16-
const statePathsCount = keys.reduce(
17-
(aggr, key) => aggr + components[key].paths.length,
18-
0
19-
)
20-
return (
21-
<Wrapper>
22-
<Panels>
23-
<Panel>
24-
<Text variant="label" dense>
25-
connected
26-
</Text>
27-
<Text variant="header" dense>
28-
{mountedKeys.length}
29-
</Text>
30-
</Panel>
31-
<Panel>
32-
<Text variant="label" dense>
33-
updates
34-
</Text>
35-
<Text variant="header" dense>
36-
{updatesCount}
37-
</Text>
38-
</Panel>
39-
<Panel>
40-
<Text variant="label" dense>
41-
values watched
42-
</Text>
43-
<Text variant="header" dense>
44-
{statePathsCount}
45-
</Text>
46-
</Panel>
47-
</Panels>
48-
<Table headers={['name', 'updates', 'values']}>
49-
{mountedKeys.map((key) => {
50-
const component = components[key]
51-
return (
52-
<Row key={component.id}>
53-
<Cell>{component.name}</Cell>
54-
<Cell>
55-
<Text variant="title" dense>
56-
{component.updateCount}
57-
</Text>
58-
</Cell>
59-
<Cell>
60-
{component.paths.map((path) => <Pill key={path}>{path}</Pill>)}
61-
</Cell>
62-
</Row>
63-
)
64-
})}
65-
</Table>
66-
</Wrapper>
67-
)
68-
}
8+
const Components: React.SFC<Connect> = ({ appState }) => (
9+
<Wrapper>
10+
<Panels>
11+
<Panel>
12+
<Text variant="label" dense>
13+
connected
14+
</Text>
15+
<Text variant="header" dense>
16+
{appState.componentsMounted.length}
17+
</Text>
18+
</Panel>
19+
<Panel>
20+
<Text variant="label" dense>
21+
updates
22+
</Text>
23+
<Text variant="header" dense>
24+
{appState.componentsUpdateCount}
25+
</Text>
26+
</Panel>
27+
<Panel>
28+
<Text variant="label" dense>
29+
values watched
30+
</Text>
31+
<Text variant="header" dense>
32+
{appState.componentsStatePathCount}
33+
</Text>
34+
</Panel>
35+
</Panels>
36+
<Table headers={['name', 'updates', 'values']}>
37+
{appState.componentsMounted.map((component) => {
38+
return (
39+
<Row key={component.id}>
40+
<Cell>{component.name}</Cell>
41+
<Cell>
42+
<Text variant="title" dense>
43+
{component.updateCount}
44+
</Text>
45+
</Cell>
46+
<Cell>
47+
{component.paths.map((path) => <Pill key={path}>{path}</Pill>)}
48+
</Cell>
49+
</Row>
50+
)
51+
})}
52+
</Table>
53+
</Wrapper>
54+
)
6955

7056
export default connect(Components)

packages/node_modules/overmind-devtools/src/client/components/Console/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import { Wrapper } from './elements'
66
class Console extends React.Component<Connect> {
77
render() {
88
const { appState } = this.props
9-
const messagesReversed = appState.apps[appState.currentPort].messages
10-
.slice()
11-
.reverse()
9+
const messagesReversed = appState.currentApp.messages.slice().reverse()
1210

1311
return (
1412
<Wrapper>

packages/node_modules/overmind/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"repository": "git+https://github.com/cerebral/overmind.git",
88
"main": "lib/index.js",
99
"module": "es/index.js",
10-
"types": "lib/bundle.d.ts",
10+
"types": "lib/index.d.ts",
1111
"scripts": {
1212
"build": "npm run build:lib & npm run build:es",
1313
"build:lib": "tsc --outDir lib --module commonjs",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import App, { derived } from './'
2+
3+
type State = {
4+
foo: string
5+
upperFoo: string
6+
}
7+
8+
describe('Derived', () => {
9+
test('should instantiate app with derived state', () => {
10+
const app = new App({
11+
state: {
12+
foo: 'bar',
13+
upperFoo: derived((state: State) => state.foo.toUpperCase()),
14+
},
15+
})
16+
expect(app.state.upperFoo).toEqual('BAR')
17+
})
18+
test('should track derived state', () => {
19+
let renderCount = 0
20+
const app = new App({
21+
state: {
22+
foo: 'bar',
23+
upperFoo: derived((state: State) => state.foo.toUpperCase()),
24+
},
25+
actions: (action) => ({
26+
changeFoo: action().mutation((_, state) => (state.foo = 'bar2')),
27+
}),
28+
})
29+
function render() {
30+
const trackId = app.trackState()
31+
app.state.upperFoo
32+
return app.clearTrackState(trackId)
33+
}
34+
app.addMutationListener(render(), () => {
35+
renderCount++
36+
})
37+
app.actions.changeFoo()
38+
expect(app.state.upperFoo).toBe('BAR2')
39+
expect(renderCount).toBe(1)
40+
})
41+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import ProxyStateTree from 'proxy-state-tree'
2+
3+
class Derived {
4+
isDirty: boolean
5+
proxyStateTreeListener: any
6+
value: any
7+
cb: (state: object) => void
8+
paths: Set<string>
9+
constructor(cb) {
10+
this.isDirty = true
11+
this.proxyStateTreeListener = null
12+
this.value = null
13+
this.cb = cb
14+
return this.evaluate.bind(this)
15+
}
16+
evaluate(proxyStateTree: ProxyStateTree) {
17+
if (this.isDirty) {
18+
const trackId = proxyStateTree.startPathsTracking()
19+
this.value = this.cb(proxyStateTree.get())
20+
this.isDirty = false
21+
this.paths = proxyStateTree.clearPathsTracking(trackId)
22+
if (this.proxyStateTreeListener) {
23+
this.proxyStateTreeListener.update(this.paths)
24+
} else {
25+
this.proxyStateTreeListener = proxyStateTree.addMutationListener(
26+
this.paths,
27+
() => {
28+
this.isDirty = true
29+
}
30+
)
31+
}
32+
}
33+
34+
// Tracks the paths for the consumer of this derived value
35+
for (let path of this.paths) {
36+
proxyStateTree.addTrackingPath(path)
37+
}
38+
39+
return this.value
40+
}
41+
}
42+
43+
export default function derived<NewValue>(
44+
cb: (state: object) => NewValue
45+
): NewValue {
46+
return (new Derived(cb) as any) as NewValue
47+
}

packages/node_modules/overmind/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import createActionFactory, {
66
NoValueAction,
77
} from './createActionFactory'
88
export { default as compose } from './compose'
9+
export { default as derived } from './derived'
910

1011
type Configuration<State, Providers, Actions> = {
1112
state?: State
@@ -178,7 +179,7 @@ export default class App<
178179
clearTrackState(id: number) {
179180
return this.proxyStateTree.clearPathsTracking(id)
180181
}
181-
addMutationListener(name, paths, cb) {
182+
addMutationListener(paths, cb) {
182183
return this.proxyStateTree.addMutationListener(paths, cb)
183184
}
184185
}

packages/node_modules/overmind/src/views/react.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,9 @@ export default class ReactApp<
9494
if (this.__mutationListener) {
9595
this.__mutationListener.update(paths)
9696
} else {
97-
this.__mutationListener = instance.addMutationListener(
98-
Component.name || '',
99-
paths,
100-
() => {
101-
this.forceUpdate()
102-
}
103-
)
97+
this.__mutationListener = instance.addMutationListener(paths, () => {
98+
this.forceUpdate()
99+
})
104100
}
105101

106102
return value
@@ -160,13 +156,9 @@ export default class ReactApp<
160156
if (this.__mutationListener) {
161157
this.__mutationListener.update(paths)
162158
} else {
163-
this.__mutationListener = instance.addMutationListener(
164-
Component.name || '',
165-
paths,
166-
() => {
167-
this.forceUpdate()
168-
}
169-
)
159+
this.__mutationListener = instance.addMutationListener(paths, () => {
160+
this.forceUpdate()
161+
})
170162
}
171163

172164
return value

packages/node_modules/overmind/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"declaration": true,
1313
"pretty": true,
1414
"sourceMap": true,
15-
"inlineSources": true
15+
"inlineSources": true,
16+
"downlevelIteration": true
1617
},
1718
"exclude": ["node_modules", "dist", "es", "lib"]
1819
}

0 commit comments

Comments
 (0)