Skip to content

Commit 9990fc4

Browse files
feat(overmind): add class replacer to rehydrate
1 parent 328f240 commit 9990fc4

File tree

4 files changed

+63
-17
lines changed

4 files changed

+63
-17
lines changed

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,27 @@ function toJSON(obj) {
55
return JSON.parse(JSON.stringify(obj))
66
}
77

8+
class StateValue {
9+
value = 'foo'
10+
toJSON() {
11+
return {
12+
value: this.value
13+
}
14+
}
15+
static fromJSON(json: { value: string }) {
16+
const instance = new StateValue()
17+
instance.value = json.value
18+
return instance
19+
}
20+
}
21+
822
function createDefaultOvermind() {
923
const state = {
1024
foo: 'bar',
1125
item: {
1226
isAwesome: true,
1327
},
28+
value: [] as StateValue[]
1429
}
1530
const changeFoo: Action<void, string> = (context) => {
1631
context.state.foo = 'bar2'
@@ -41,7 +56,9 @@ function createDefaultOvermind() {
4156
form[key] = value
4257
}
4358
const rehydrateAction: Action<any> = ({ state }, newState) => {
44-
rehydrate(state, newState)
59+
rehydrate(state, newState, {
60+
value: (json) => StateValue.fromJSON(json)
61+
})
4562
}
4663
const actions = {
4764
asyncChangeFoo,
@@ -361,8 +378,8 @@ describe('Overmind', () => {
361378
).not.toThrow()
362379
expect(app.state.item.isAwesome).toBe(false)
363380
})
364-
test('should allow rehydration', () => {
365-
expect.assertions(2)
381+
test.only('should allow rehydration', () => {
382+
expect.assertions(4)
366383
const app = createDefaultOvermind()
367384
const mutation = {
368385
method: 'set',
@@ -374,13 +391,17 @@ describe('Overmind', () => {
374391
app.actions.rehydrateAction({
375392
item: {
376393
isAwesome: false
377-
}
394+
},
395+
value: [{
396+
value: 'bar'
397+
}]
378398
})
379-
expect(app.state).toEqual({
380-
foo: 'bar2',
381-
item: {
382-
isAwesome: false
383-
}
399+
expect(app.state.foo).toEqual('bar2')
400+
expect(app.state.item).toEqual({
401+
isAwesome: false
402+
})
403+
expect(app.state.value[0].toJSON()).toEqual({
404+
value: 'bar'
384405
})
385406
})
386407
})

packages/node_modules/overmind/src/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import {
2121
Execution,
2222
NestedPartial,
2323
Options,
24+
RehydrateClasses,
2425
ResolveActions,
2526
ResolveState,
2627
SSRMode,
27-
TestMode,
28+
TestMode
2829
} from './internalTypes'
2930
import {
3031
createContext,
@@ -93,24 +94,33 @@ export interface Reaction extends IReaction<Config> {}
9394
export const json = (obj: any) =>
9495
deepCopy(obj && obj[IS_PROXY] ? obj[VALUE] : obj)
9596

96-
export const rehydrate = (state: IState, source: IMutation[] | IState) => {
97+
export const rehydrate = <T extends IState>(state: T, source: IMutation[] | IState, classes: RehydrateClasses<T> = {} as RehydrateClasses<T>) => {
9798
if (Array.isArray(source)) {
9899
const mutations = source as IMutation[]
99100
mutations.forEach((mutation) => {
100101
const pathArray = mutation.path.split('.')
101102
const key = pathArray.pop() as string
102103
const target = pathArray.reduce((aggr, key) => aggr[key], state as any)
104+
const classInstance = pathArray.reduce((aggr, key) => aggr[key], classes as any)
103105

104106
if (mutation.method === 'set') {
105-
target[key] = mutation.args[0]
107+
if (typeof classInstance === 'function' && Array.isArray(mutation.args[0])) {
108+
target[key] = mutation.args[0].map((arg) => classInstance(arg))
109+
} else if (typeof classInstance === 'function') {
110+
target[key] = classInstance(mutation.args[0])
111+
} else {
112+
target[key] = mutation.args[0]
113+
}
106114
} else if (mutation.method === 'unset') {
107115
delete target[key]
108116
} else {
109-
target[key][mutation.method](...mutation.args)
117+
target[key][mutation.method].apply(target[key], typeof classInstance === 'function' ? mutation.args.map((arg) => {
118+
return typeof arg === 'object' && arg !== null ? classInstance(arg) : arg
119+
}) : mutation.args)
110120
}
111121
})
112122
} else {
113-
rehydrateState(state, source)
123+
rehydrateState(state, source, classes)
114124
}
115125
}
116126

packages/node_modules/overmind/src/internalTypes.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {
2+
IFlushCallback,
23
IMutation,
34
IMutationTree,
45
ITrackStateTree,
5-
IFlushCallback,
66
} from 'proxy-state-tree'
7+
78
import { IAction, IOperator, IState } from './types'
89

910
export type SubType<Base, Condition> = Pick<
@@ -15,6 +16,14 @@ export type NestedPartial<T> = T extends Function
1516
? T
1617
: Partial<{ [P in keyof T]: NestedPartial<T[P]> }>
1718

19+
export type RehydrateClasses<T extends IState> = Pick<{
20+
[P in keyof T]: T[P] extends { toJSON: () => infer U } ? (data: U) => T[P] :
21+
T[P] extends Array<{ toJSON: () => infer U }> ? (data: U) => T[P][0] :
22+
T[P] extends { [key: string]: { toJSON: () => infer U } } ? (data: U) => T[P][0] :
23+
T[P] extends IState ? RehydrateClasses<T[P]> :
24+
never
25+
},{ [Key in keyof T]: T[Key] extends { toJSON: () => any } ? Key : never }[keyof T]>
26+
1827
export type Options = {
1928
name?: string
2029
devtools?: string | boolean

packages/node_modules/overmind/src/utils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import isPlainObject from 'is-plain-obj'
22

33
import { Derived } from './derived'
4+
import { RehydrateClasses } from './internalTypes'
45
import { IState } from './types'
56

67
export const IS_TEST = process.env.NODE_ENV === 'test'
@@ -155,16 +156,21 @@ export function createActionsProxy(actions, cb) {
155156
})
156157
}
157158

158-
export function rehydrateState(target: IState, source: IState) {
159+
export function rehydrateState<T extends IState>(target: T, source: IState, classes: RehydrateClasses<T> = {} as RehydrateClasses<T>) {
159160
if (!target || !source) {
160161
throw new Error(`You have to pass a "target" and "source" object to rehydrate`)
161162
}
162163

163164
Object.keys(source).forEach((key) => {
164165
const value = source[key]
166+
const classInstance = classes[key]
165167
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
166168
if (!target[key]) target[key] = {}
167-
rehydrateState(target[key] as IState, source[key] as IState)
169+
rehydrateState(target[key] as IState, source[key] as IState, classes[key])
170+
} else if (classInstance && Array.isArray(source[key])) {
171+
target[key] = (source[key] as any[]).map(value => classInstance(value))
172+
} else if (classInstance) {
173+
target[key] = classInstance(source[key])
168174
} else {
169175
target[key] = source[key]
170176
}

0 commit comments

Comments
 (0)