Skip to content

Commit 2e105df

Browse files
feat(overmind): use mutation rehydration for state hot reloading
1 parent 01000e8 commit 2e105df

File tree

3 files changed

+47
-48
lines changed

3 files changed

+47
-48
lines changed

packages/node_modules/overmind/src/index.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,18 @@ describe('Overmind', () => {
382382
).not.toThrow()
383383
expect(app.state.item.isAwesome).toBe(false)
384384
})
385+
test('should rehydrate mutations on hot reload', () => {
386+
expect.assertions(2)
387+
const app = createDefaultOvermind()
388+
app.actions.changeFoo()
389+
expect(app.state.foo).toBe('bar2')
390+
app.reconfigure({
391+
state: {
392+
foo: 'bar'
393+
}
394+
})
395+
expect(app.state.foo).toBe('bar2')
396+
})
385397
})
386398

387399
describe('Overmind mock', () => {

packages/node_modules/overmind/src/index.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import {
5959
getActionPaths,
6060
getFunctionName,
6161
isPromise,
62-
mergeState,
62+
runMutation,
6363
processState,
6464
} from './utils'
6565

@@ -189,7 +189,7 @@ export class Overmind<ThisConfig extends IConfiguration>
189189
private actionReferences: Function[] = []
190190
private nextExecutionId: number = 0
191191
private mode: DefaultMode | TestMode | SSRMode
192-
private originalConfiguration
192+
private reydrateMutationsForHotReloading: IMutation[] = []
193193
initialized: Promise<any>
194194
eventHub: EventEmitter<Events>
195195
devtools: Devtools
@@ -238,8 +238,6 @@ export class Overmind<ThisConfig extends IConfiguration>
238238
eventHub,
239239
mode.mode === MODE_SSR ? false : process.env.NODE_ENV === 'development'
240240
)
241-
242-
this.originalConfiguration = configuration
243241
this.state = proxyStateTree.state
244242
this.effects = configuration.effects || {}
245243
this.proxyStateTree = proxyStateTree
@@ -324,6 +322,11 @@ export class Overmind<ThisConfig extends IConfiguration>
324322
nextTick = setTimeout(flushTree, 0)
325323
})
326324
} else if (mode.mode === MODE_DEFAULT || mode.mode === MODE_TEST) {
325+
if (process.env.NODE_ENV === 'test' || (this.devtools && options.hotReloading !== false)) {
326+
eventHub.on(EventType.MUTATIONS, (execution) => {
327+
this.reydrateMutationsForHotReloading = this.reydrateMutationsForHotReloading.concat(execution.mutations)
328+
})
329+
}
327330
eventHub.on(EventType.OPERATOR_ASYNC, (execution) => {
328331
const flushData = execution.flush(true)
329332
if (this.devtools && flushData.mutations.length) {
@@ -942,21 +945,22 @@ export class Overmind<ThisConfig extends IConfiguration>
942945
return this.proxyStateTree.onFlush(cb)
943946
}
944947
reconfigure(configuration: IConfiguration) {
945-
const mergedConfiguration = {
946-
...configuration,
947-
state: mergeState(
948-
this.originalConfiguration.state,
949-
this.state,
950-
configuration.state
951-
),
952-
}
953948
const proxyStateTree = this.proxyStateTree as any
954-
this.originalConfiguration.state = configuration.state
955-
this.proxyStateTree.sourceState = this.getState(mergedConfiguration)
949+
this.proxyStateTree.sourceState = this.getState(configuration)
956950
proxyStateTree.createTrackStateProxifier()
957951
this.state = this.proxyStateTree.state as any
958-
this.actions = this.getActions(mergedConfiguration.actions)
959-
this.effects = mergedConfiguration.effects || {}
952+
this.actions = this.getActions(configuration.actions)
953+
this.effects = configuration.effects || {}
954+
955+
this.reydrateMutationsForHotReloading.forEach((mutation) => {
956+
try {
957+
runMutation(this.state, mutation)
958+
} catch (error) {
959+
// No worries, structure changed and we do not want to mutate anyways
960+
}
961+
})
962+
963+
this.reydrateMutationsForHotReloading.length = 0
960964

961965
this.proxyStateTree.forceFlush()
962966

packages/node_modules/overmind/src/utils.ts

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -90,41 +90,24 @@ export function deepCopy(obj) {
9090
return obj
9191
}
9292

93-
export function mergeState(originState, oldState, nextState) {
94-
function merge(origin, old, next) {
95-
if (isPlainObject(old) && isPlainObject(next)) {
96-
const newBranch = {}
97-
const keys = Object.keys(old).concat(Object.keys(next))
98-
99-
for (let key of keys) {
100-
newBranch[key] = merge(origin[key], old[key], next[key])
101-
}
102-
103-
return newBranch
104-
}
105-
106-
if (typeof next === 'function') {
107-
return next
108-
}
109-
110-
// We return the existing array, as arrays are typically
111-
// mutated, not set with new values as initial state
112-
if (Array.isArray(old) && Array.isArray(next)) {
113-
return old
114-
}
115-
116-
// If we have changed a state from origin, keep that
117-
// changed state
118-
if (next === origin && old !== origin) {
119-
return old
120-
}
121-
122-
return next
93+
export const runMutation = (state: any, mutation: IMutation) => {
94+
const pathArray = mutation.path.split(mutation.delimiter)
95+
const key = pathArray.pop() as string
96+
const target = pathArray.reduce((current, pathKey) => current[pathKey].__CLASS__ ? current[pathKey].value : current[pathKey], state)
97+
98+
switch (mutation.method) {
99+
case 'set':
100+
target[key] = mutation.args[0]
101+
break
102+
case 'unset':
103+
delete target[key]
104+
break
105+
default:
106+
target[key][mutation.method](...mutation.args)
123107
}
124-
125-
return merge(originState, oldState, nextState)
126108
}
127109

110+
128111
export function getActionPaths(actions = {}, currentPath: string[] = []) {
129112
return Object.keys(actions).reduce<string[]>((aggr, key) => {
130113
if (typeof actions[key] === 'function') {

0 commit comments

Comments
 (0)