Skip to content

Commit d6200bd

Browse files
committed
Add reactor.replaceStores function
This allows stores to be hot reloadable
1 parent 6342f62 commit d6200bd

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

src/reactor.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,22 @@ class Reactor {
158158
}
159159

160160
/**
161-
* @param {Store[]} stores
161+
* @param {Object} stores
162162
*/
163163
registerStores(stores) {
164164
this.reactorState = fns.registerStores(this.reactorState, stores)
165165
this.__notify()
166166
}
167167

168+
/**
169+
* Replace store implementation (handlers) without modifying the app state or calling getInitialState
170+
* Useful for hot reloading
171+
* @param {Object} stores
172+
*/
173+
replaceStores(stores) {
174+
this.reactorState = fns.replaceStores(this.reactorState, stores)
175+
}
176+
168177
/**
169178
* Returns a plain object representing the application state
170179
* @return {Object}

src/reactor/fns.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ export function registerStores(reactorState, stores) {
4848
})
4949
}
5050

51+
/**
52+
* Overrides the store implementation without resetting the value of that particular part of the app state
53+
* this is useful when doing hot reloading of stores.
54+
* @param {ReactorState} reactorState
55+
* @param {Object<String, Store>} stores
56+
* @return {ReactorState}
57+
*/
58+
export function replaceStores(reactorState, stores) {
59+
return reactorState.withMutations((reactorState) => {
60+
each(stores, (store, id) => {
61+
const initialState = store.getInitialState()
62+
63+
if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
64+
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
65+
}
66+
67+
reactorState
68+
.update('stores', stores => stores.set(id, store))
69+
})
70+
})
71+
}
72+
5173
/**
5274
* @param {ReactorState} reactorState
5375
* @param {String} actionType

tests/reactor-fns-tests.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,56 @@ describe('reactor fns', () => {
7474
})
7575
})
7676

77+
describe('#registerStores', () => {
78+
let reactorState
79+
let store1
80+
let store2
81+
let newStore1
82+
let originalReactorState
83+
let nextReactorState
84+
85+
beforeEach(() => {
86+
reactorState = new ReactorState()
87+
store1 = new Store({
88+
getInitialState() {
89+
return toImmutable({
90+
foo: 'bar',
91+
})
92+
},
93+
})
94+
store2 = new Store({
95+
getInitialState() {
96+
return 2
97+
},
98+
})
99+
100+
newStore1 = new Store({
101+
getInitialState() {
102+
return toImmutable({
103+
foo: 'newstore',
104+
})
105+
},
106+
})
107+
108+
originalReactorState = fns.registerStores(reactorState, {
109+
store1,
110+
store2,
111+
})
112+
113+
nextReactorState = fns.replaceStores(originalReactorState, {
114+
store1: newStore1
115+
})
116+
})
117+
118+
it('should update reactorState.stores', () => {
119+
const expectedReactorState = originalReactorState.set('stores', Map({
120+
store1: newStore1,
121+
store2: store2,
122+
}))
123+
expect(is(nextReactorState, expectedReactorState)).toBe(true)
124+
})
125+
})
126+
77127
describe('#dispatch', () => {
78128
let initialReactorState, store1, store2
79129
let nextReactorState

tests/reactor-tests.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,4 +1531,85 @@ describe('Reactor', () => {
15311531
expect(output).toEqual([6, 5])
15321532
})
15331533
})
1534+
1535+
describe('#replaceStores', () => {
1536+
let counter1Store
1537+
let counter2Store
1538+
let counter1StoreReplaced
1539+
let reactor
1540+
1541+
beforeEach(() => {
1542+
reactor = new Reactor()
1543+
counter1Store = new Store({
1544+
getInitialState: () => 1,
1545+
initialize() {
1546+
this.on('increment1', (state) => state + 1)
1547+
}
1548+
})
1549+
counter2Store = new Store({
1550+
getInitialState: () => 1,
1551+
initialize() {
1552+
this.on('increment2', (state) => state + 1)
1553+
}
1554+
})
1555+
1556+
reactor.registerStores({
1557+
counter1: counter1Store,
1558+
counter2: counter2Store,
1559+
})
1560+
})
1561+
1562+
it('should replace the store implementation without mutating the value', () => {
1563+
let newStore = new Store({
1564+
getInitialState: () => 1,
1565+
initialize() {
1566+
this.on('increment1', (state) => state + 10)
1567+
}
1568+
})
1569+
1570+
expect(reactor.evaluate(['counter1'])).toBe(1)
1571+
reactor.dispatch('increment1')
1572+
expect(reactor.evaluate(['counter1'])).toBe(2)
1573+
1574+
reactor.replaceStores({
1575+
counter1: newStore,
1576+
})
1577+
1578+
expect(reactor.evaluate(['counter1'])).toBe(2)
1579+
reactor.dispatch('increment1')
1580+
expect(reactor.evaluate(['counter1'])).toBe(12)
1581+
expect(reactor.evaluate(['counter2'])).toBe(1)
1582+
})
1583+
1584+
it('should replace multiple stores', () => {
1585+
let newStore1 = new Store({
1586+
getInitialState: () => 1,
1587+
initialize() {
1588+
this.on('increment1', (state) => state + 10)
1589+
}
1590+
})
1591+
let newStore2 = new Store({
1592+
getInitialState: () => 1,
1593+
initialize() {
1594+
this.on('increment2', (state) => state + 20)
1595+
}
1596+
})
1597+
1598+
expect(reactor.evaluate(['counter1'])).toBe(1)
1599+
reactor.dispatch('increment1')
1600+
expect(reactor.evaluate(['counter1'])).toBe(2)
1601+
1602+
reactor.replaceStores({
1603+
counter1: newStore1,
1604+
counter2: newStore2,
1605+
})
1606+
1607+
expect(reactor.evaluate(['counter1'])).toBe(2)
1608+
expect(reactor.evaluate(['counter2'])).toBe(1)
1609+
reactor.dispatch('increment1')
1610+
reactor.dispatch('increment2')
1611+
expect(reactor.evaluate(['counter1'])).toBe(12)
1612+
expect(reactor.evaluate(['counter2'])).toBe(21)
1613+
})
1614+
})
15341615
})

0 commit comments

Comments
 (0)