Skip to content

Commit

Permalink
Add reactor.replaceStores function
Browse files Browse the repository at this point in the history
This allows stores to be hot reloadable
  • Loading branch information
jordangarcia committed Dec 23, 2015
1 parent 6342f62 commit d6200bd
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/reactor.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,22 @@ class Reactor {
}

/**
* @param {Store[]} stores
* @param {Object} stores
*/
registerStores(stores) {
this.reactorState = fns.registerStores(this.reactorState, stores)
this.__notify()
}

/**
* Replace store implementation (handlers) without modifying the app state or calling getInitialState
* Useful for hot reloading
* @param {Object} stores
*/
replaceStores(stores) {
this.reactorState = fns.replaceStores(this.reactorState, stores)
}

/**
* Returns a plain object representing the application state
* @return {Object}
Expand Down
22 changes: 22 additions & 0 deletions src/reactor/fns.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ export function registerStores(reactorState, stores) {
})
}

/**
* Overrides the store implementation without resetting the value of that particular part of the app state
* this is useful when doing hot reloading of stores.
* @param {ReactorState} reactorState
* @param {Object<String, Store>} stores
* @return {ReactorState}
*/
export function replaceStores(reactorState, stores) {
return reactorState.withMutations((reactorState) => {
each(stores, (store, id) => {
const initialState = store.getInitialState()

This comment has been minimized.

Copy link
@bhamodi

bhamodi Dec 24, 2015

Contributor

Should this be or?

This comment has been minimized.

Copy link
@jordangarcia

jordangarcia Dec 27, 2015

Author Contributor

nah, only if the option is enabeld do we want to throw if the store returns a non immutable value.

This comment has been minimized.

Copy link
@bhamodi

bhamodi Dec 27, 2015

Contributor

Ah, makes sense. 👍

if (getOption(reactorState, 'throwOnNonImmutableStore') && !isImmutableValue(initialState)) {
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
}

reactorState
.update('stores', stores => stores.set(id, store))
})
})
}

/**
* @param {ReactorState} reactorState
* @param {String} actionType
Expand Down
50 changes: 50 additions & 0 deletions tests/reactor-fns-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,56 @@ describe('reactor fns', () => {
})
})

describe('#registerStores', () => {
let reactorState
let store1
let store2
let newStore1
let originalReactorState
let nextReactorState

beforeEach(() => {
reactorState = new ReactorState()
store1 = new Store({
getInitialState() {
return toImmutable({
foo: 'bar',
})
},
})
store2 = new Store({
getInitialState() {
return 2
},
})

newStore1 = new Store({
getInitialState() {
return toImmutable({
foo: 'newstore',
})
},
})

originalReactorState = fns.registerStores(reactorState, {
store1,
store2,
})

nextReactorState = fns.replaceStores(originalReactorState, {
store1: newStore1
})
})

it('should update reactorState.stores', () => {
const expectedReactorState = originalReactorState.set('stores', Map({
store1: newStore1,
store2: store2,
}))
expect(is(nextReactorState, expectedReactorState)).toBe(true)
})
})

describe('#dispatch', () => {
let initialReactorState, store1, store2
let nextReactorState
Expand Down
81 changes: 81 additions & 0 deletions tests/reactor-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1531,4 +1531,85 @@ describe('Reactor', () => {
expect(output).toEqual([6, 5])
})
})

describe('#replaceStores', () => {
let counter1Store
let counter2Store
let counter1StoreReplaced
let reactor

beforeEach(() => {
reactor = new Reactor()
counter1Store = new Store({
getInitialState: () => 1,
initialize() {
this.on('increment1', (state) => state + 1)
}
})
counter2Store = new Store({
getInitialState: () => 1,
initialize() {
this.on('increment2', (state) => state + 1)
}
})

reactor.registerStores({
counter1: counter1Store,
counter2: counter2Store,
})
})

it('should replace the store implementation without mutating the value', () => {
let newStore = new Store({
getInitialState: () => 1,
initialize() {
this.on('increment1', (state) => state + 10)
}
})

expect(reactor.evaluate(['counter1'])).toBe(1)
reactor.dispatch('increment1')
expect(reactor.evaluate(['counter1'])).toBe(2)

reactor.replaceStores({
counter1: newStore,
})

expect(reactor.evaluate(['counter1'])).toBe(2)
reactor.dispatch('increment1')
expect(reactor.evaluate(['counter1'])).toBe(12)
expect(reactor.evaluate(['counter2'])).toBe(1)
})

it('should replace multiple stores', () => {
let newStore1 = new Store({
getInitialState: () => 1,
initialize() {
this.on('increment1', (state) => state + 10)
}
})
let newStore2 = new Store({
getInitialState: () => 1,
initialize() {
this.on('increment2', (state) => state + 20)
}
})

expect(reactor.evaluate(['counter1'])).toBe(1)
reactor.dispatch('increment1')
expect(reactor.evaluate(['counter1'])).toBe(2)

reactor.replaceStores({
counter1: newStore1,
counter2: newStore2,
})

expect(reactor.evaluate(['counter1'])).toBe(2)
expect(reactor.evaluate(['counter2'])).toBe(1)
reactor.dispatch('increment1')
reactor.dispatch('increment2')
expect(reactor.evaluate(['counter1'])).toBe(12)
expect(reactor.evaluate(['counter2'])).toBe(21)
})
})
})

0 comments on commit d6200bd

Please sign in to comment.