diff --git a/src/utils/Subscription.js b/src/utils/Subscription.js index 064102ea1..e03f4838a 100644 --- a/src/utils/Subscription.js +++ b/src/utils/Subscription.js @@ -4,36 +4,46 @@ import { getBatch } from './batch' // well as nesting subscriptions of descendant components, so that we can ensure the // ancestor components re-render before descendants +const CLEARED = null const nullListeners = { notify() {} } function createListenerCollection() { const batch = getBatch() - let listeners = {} - let id = 0 + // the current/next pattern is copied from redux's createStore code. + // TODO: refactor+expose that code to be reusable here? + let current = [] + let next = [] return { clear() { - listeners = {} + next = CLEARED + current = CLEARED }, notify() { + const listeners = (current = next) batch(() => { - for (const id in listeners) { - listeners[id]() + for (let i = 0; i < listeners.length; i++) { + listeners[i]() } }) }, get() { - return listeners + return next }, subscribe(listener) { - const currentId = id++ - listeners[currentId] = listener + let isSubscribed = true + if (next === current) next = current.slice() + next.push(listener) return function unsubscribe() { - delete listeners[currentId] + if (!isSubscribed || current === CLEARED) return + isSubscribed = false + + if (next === current) next = current.slice() + next.splice(next.indexOf(listener), 1) } } } diff --git a/test/hooks/useSelector.spec.js b/test/hooks/useSelector.spec.js index 51fb76532..b0c8692b0 100644 --- a/test/hooks/useSelector.spec.js +++ b/test/hooks/useSelector.spec.js @@ -97,11 +97,11 @@ describe('React', () => { ) - expect(Object.keys(rootSubscription.listeners.get()).length).toBe(1) + expect(rootSubscription.listeners.get().length).toBe(1) store.dispatch({ type: '' }) - expect(Object.keys(rootSubscription.listeners.get()).length).toBe(2) + expect(rootSubscription.listeners.get().length).toBe(2) }) it('unsubscribes when the component is unmounted', () => { @@ -125,11 +125,11 @@ describe('React', () => { ) - expect(Object.keys(rootSubscription.listeners.get()).length).toBe(2) + expect(rootSubscription.listeners.get().length).toBe(2) store.dispatch({ type: '' }) - expect(Object.keys(rootSubscription.listeners.get()).length).toBe(1) + expect(rootSubscription.listeners.get().length).toBe(1) }) it('notices store updates between render and store subscription effect', () => {