Skip to content

Commit

Permalink
Memoize setter to prevent erroneous re-renders (fix #35)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcherny committed May 27, 2018
1 parent 276efad commit 9c968e6
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 7 deletions.
23 changes: 17 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as RxJS from 'rxjs'
import { Emitter } from 'typed-rx-emitter'
import { withReduxDevtools } from './plugins/reduxDevtools'
import { mapValues } from './utils'

export type Undux<Actions extends object> = {
[K in keyof Actions]: {
Expand Down Expand Up @@ -49,8 +50,23 @@ export class StoreDefinition<Actions extends object> implements Store<Actions> {
private store: StoreSnapshot<Actions>
private alls: Emitter<Undux<Actions>> = new Emitter
private emitter: Emitter<Actions> = new Emitter
private setters: {
readonly [K in keyof Actions]: (value: Actions[K]) => void
}
constructor(state: Actions) {

// Set initial state
this.store = new StoreSnapshot(state, this)

// Cache setters
this.setters = mapValues(state, (v, key) =>
(value: typeof v) => {
let previousValue = this.store.get(key)
this.store = this.store['assign'](key, value)
this.emitter.emit(key, value)
this.alls.emit(key, { key, previousValue, value })
}
)
}
on<K extends keyof Actions>(key: K): RxJS.Observable<Actions[K]> {
return this.emitter.on(key)
Expand All @@ -62,12 +78,7 @@ export class StoreDefinition<Actions extends object> implements Store<Actions> {
return this.store.get(key)
}
set<K extends keyof Actions>(key: K) {
return (value: Actions[K]) => {
let previousValue = this.store.get(key)
this.store = this.store['assign'](key, value)
this.emitter.emit(key, value)
this.alls.emit(key, { key, previousValue, value })
}
return this.setters[key]
}
getState() {
return this.store.getState()
Expand Down
16 changes: 16 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ export function isImmutable(a: any): a is Immutable {
export function getDisplayName<T>(Component: ComponentType<T>): string {
return Component.displayName || Component.name || 'Component'
}

export function mapValues<O extends object, K extends keyof O, T>(
o: O,
f: (value: O[K], key: K) => T
): {[K in keyof O]: T} {
let result: {[K in keyof O]: T} = {} as any
keys(o).forEach(k =>
result[k] = f(o[k] as any, k as any) // TODO: Improve this
)
return result
}

// Strict Object.keys
function keys<O extends object>(o: O): (keyof O)[] {
return Object.keys(o) as any
}
7 changes: 7 additions & 0 deletions test/stateful.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,10 @@ test('[stateful] it should update correctly when using nested stores', t => {
t.is(_.innerHTML, '<div>3-4</div>')
})
})

test('[stateful] it should memoize setters', t =>
withElement(MyComponentWithLens, _ => {
t.is(store.set('isTrue'), store.set('isTrue'))
t.is(store.set('users'), store.set('users'))
})
)
7 changes: 7 additions & 0 deletions test/stateless.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,10 @@ test('[stateless] it should update correctly when using nested stores', t => {
t.is(_.innerHTML, '<div>3-4</div>')
})
})

test('[stateless] it should memoize setters', t =>
withElement(MyComponentWithLens, _ => {
t.is(store.set('isTrue'), store.set('isTrue'))
t.is(store.set('users'), store.set('users'))
})
)
9 changes: 8 additions & 1 deletion test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test } from 'ava'
import * as Immutable from 'immutable'
import { isImmutable } from '../src/utils'
import { isImmutable, mapValues } from '../src/utils'

test('isImmutable', t => {
t.is(isImmutable(Immutable.List()), true)
Expand All @@ -26,3 +26,10 @@ test('isImmutable', t => {
t.is(isImmutable('a'), false)
t.is(isImmutable(42), false)
})

test('mapValues', t => {
t.deepEqual(mapValues({}, _ => _ * 2), {})
t.deepEqual(mapValues({ a: 1 }, _ => _ * 2), { a: 2 })
t.deepEqual(mapValues({ a: 1, b: 2 }, _ => _ * 2), { a: 2, b: 4 })
t.deepEqual(mapValues({ a: 1, b: 2 }, (_, k) => k), { a: 'a', b: 'b' })
})

0 comments on commit 9c968e6

Please sign in to comment.