From 89be1f1f0e3a13e6fab33d08e1570978fd02e422 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 24 Mar 2018 17:28:31 -0400 Subject: [PATCH 1/2] allow to unsubscribe a particular callback For #1. With this patch we can now unsubscribe either for a given action or a given callback across actions, or a mix of the two. --- Readme.md | 15 ++++++++-- __tests__/index.js | 56 +++++++++++++++++++++++++++++++++- index.js | 75 +++++++++++++++++++++++++++++----------------- 3 files changed, 116 insertions(+), 30 deletions(-) diff --git a/Readme.md b/Readme.md index cace503..5ec06ee 100644 --- a/Readme.md +++ b/Readme.md @@ -12,6 +12,7 @@ Subscribe and UnSubscribe action for Redux ## Usage For Middleware + ```js import {createStore} from 'redux'; import Subscriber from 'redux-subscriber-middleware'; @@ -28,6 +29,7 @@ let store = createStore( ``` Inside the script + ```js // or you can just import "subscribe" function from the package import { subscribeAction, subscribeOnceAction, unsubscribeAction } from 'redux-subscriber-middleware'; @@ -38,8 +40,17 @@ dispatch(subscribeAction('ACTION_YOU_WANT_TO_SUBSCRIBE', CALLBACK_FUNCTION)); // for one time dispatch(subscribeOnceAction('ACTION_YOU_WANT_TO_SUBSCRIBE', CALLBACK_FUNCTION)); -// for unsubscribe -dispatch(unsubscribeAction('ACTION_YOU_WANT_TO_SUBSCRIBE')); +// unsubscribe all callbacks for action +dispatch(unsubscribeAction('ACTION_YOU_WANT_TO_UNSUBSCRIBE')); + +// unsubscribe specific callback for action +dispatch(unsubscribeAction('ACTION_YOU_WANT_TO_UNSUBSCRIBE', CALLBACK_FUNCTION)); + +// unsubscribe specific callback for all actions +dispatch(unsubscribeAction(null, CALLBACK_FUNCTION)); + +// unsubscribe everything +dispatch(unsubscribeAction()); ``` diff --git a/__tests__/index.js b/__tests__/index.js index 4575da8..4d87ce0 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -3,7 +3,8 @@ import Subscriber, { subscribeAction, subscribeOnceAction, unsubscribeAction } f let store; let mockCallback; -const addAction = () => ({ type: 'ADD_ACTION' }); +const addAction = () => ({ type: 'ADD_ACTION' }); +const otherAction = () => ({ type: 'OTHER_ACTION' }); function initialize() { const middlewares = [Subscriber()]; @@ -95,5 +96,58 @@ describe('redux-subscriber-middleware', () => { store.dispatch(addAction()); expect(mockCallback.mock.calls.length).toEqual(1); }); + + it('should unsubscribe the specific callback given', () => { + let otherCallback = jest.fn(); + let onceCallback = jest.fn(); + + store.dispatch(subscribeAction('ADD_ACTION', mockCallback)); + store.dispatch(subscribeAction('ADD_ACTION', otherCallback)); + store.dispatch(addAction()); + + store.dispatch(subscribeOnceAction('ADD_ACTION', onceCallback)); + expect(onceCallback).not.toBeCalled(); + store.dispatch(unsubscribeAction('ADD_ACTION', otherCallback)); + store.dispatch(unsubscribeAction('ADD_ACTION', onceCallback)); + + store.dispatch(addAction()); + + expect(otherCallback.mock.calls.length).toEqual(1); + expect(mockCallback.mock.calls.length).toEqual(2); + }); + + it('should unsubscribe the specific callback given for all actions', () => { + let onceCallback = jest.fn(); + + store.dispatch(subscribeAction('ADD_ACTION', mockCallback)); + store.dispatch(subscribeAction('OTHER_ACTION', mockCallback)); + store.dispatch(addAction()); + store.dispatch(otherAction()); + + store.dispatch(subscribeOnceAction('ADD_ACTION', onceCallback)); + expect(onceCallback).not.toBeCalled(); + store.dispatch(unsubscribeAction(null, mockCallback)); + store.dispatch(unsubscribeAction(null, onceCallback)); + + store.dispatch(addAction()); + store.dispatch(otherAction()); + + expect(mockCallback.mock.calls.length).toEqual(2); + }); + + it('should unsubscribe ALL THE THINGS', () => { + let otherCallback = jest.fn(); + + store.dispatch(subscribeAction('ADD_ACTION', mockCallback)); + store.dispatch(subscribeAction('OTHER_ACTION', mockCallback)); + store.dispatch(subscribeAction('OTHER_ACTION', otherCallback)); + store.dispatch(addAction()); + store.dispatch(otherAction()); + store.dispatch(unsubscribeAction()); + store.dispatch(addAction()); + store.dispatch(otherAction()); + expect(mockCallback.mock.calls.length).toEqual(2); + expect(otherCallback.mock.calls.length).toEqual(1); + }); }); }); diff --git a/index.js b/index.js index d2a1397..0e2ce60 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -'use strict'; + export const SUBSCRIBE = 'REDUX_ACTION_SUBSCRIBE'; export const SUBSCRIBEONCE = 'REDUX_ACTION_SUBSCRIBEONCE'; @@ -20,25 +20,46 @@ export default () => { } } - return store => next => (action) => { + return store => next => (action) => { // eslint-disable-line no-unused-vars const result = next(action); if (action.type === SUBSCRIBE || action.type === SUBSCRIBEONCE || action.type === UNSUBSCRIBE) { switch (action.type) { - case SUBSCRIBE: - subscribeHandler(subscribe, action.payload.event, action.payload.callback ,true); - break; - case SUBSCRIBEONCE: - subscribeHandler(subscribeOnce, action.payload.event, action.payload.callback); - break; - case UNSUBSCRIBE: - if (subscribe[action.payload.event]) { - delete subscribe[action.payload.event]; - } - if (subscribeOnce[action.payload.event]) { - delete subscribeOnce[action.payload.event]; - } - break; - default: + case SUBSCRIBE: + subscribeHandler(subscribe, action.payload.event, action.payload.callback, true); + break; + case SUBSCRIBEONCE: + subscribeHandler(subscribeOnce, action.payload.event, action.payload.callback); + break; + case UNSUBSCRIBE: // eslint-disable-line no-case-declarations + const { event, callback } = action.payload; + if (event) { + if (callback) { + if (subscribe[event]) { + subscribe[event] = subscribe[event].filter(cb => cb !== callback); + } + + if (subscribeOnce[event]) { + subscribeOnce[event] = subscribeOnce[event].filter(cb => cb !== callback); + } + } else { + delete subscribe[event]; + delete subscribeOnce[event]; + } + } else if (callback) { + // eslint-disable-next-line no-return-assign + Object.keys(subscribe).forEach(k => + subscribe[k] = subscribe[k].filter(cb => cb !== callback), + ); + // eslint-disable-next-line no-return-assign + Object.keys(subscribeOnce).forEach(k => + subscribeOnce[k] = subscribeOnce[k].filter(cb => cb !== callback), + ); + } else { + Object.keys(subscribe).forEach(k => delete subscribe[k]); + Object.keys(subscribeOnce).forEach(k => delete subscribeOnce[k]); + } + break; + default: } } else { actionList.push(action.type); @@ -58,21 +79,21 @@ export const subscribeAction = (event, callback) => ({ type: SUBSCRIBE, payload: { event, - callback - } + callback, + }, }); export const subscribeOnceAction = (event, callback) => ({ type: SUBSCRIBEONCE, payload: { event, - callback - } + callback, + }, }); -export const unsubscribeAction = event => ({ - type: UNSUBSCRIBE, - payload: { - event - } -}); +export const unsubscribeAction = (event, callback = null) => { + const action = { type: UNSUBSCRIBE, payload: { } }; + if (event) action.payload.event = event; + if (callback) action.payload.callback = callback; + return action; +}; From d7f9265dc7e0667cfa50df244aec74d9231f6874 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sat, 24 Mar 2018 17:29:43 -0400 Subject: [PATCH 2/2] remove the actionList ...as this seemed to be calling the callback even if the subscription happens after the dispatch. Which feels helluvah weird. --- __tests__/index.js | 4 ++-- index.js | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/__tests__/index.js b/__tests__/index.js index 4d87ce0..18ea327 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -78,12 +78,12 @@ describe('redux-subscriber-middleware', () => { beforeEach(() => { initialize(); }); - it('should call the callback function one time', () => { + it('should call the callback function two times', () => { store.dispatch(addAction()); store.dispatch(subscribeAction('ADD_ACTION', mockCallback)); store.dispatch(addAction()); store.dispatch(addAction()); - expect(mockCallback.mock.calls.length).toEqual(3); + expect(mockCallback.mock.calls.length).toEqual(2); }); }); }); diff --git a/index.js b/index.js index 0e2ce60..dc9a25d 100644 --- a/index.js +++ b/index.js @@ -5,18 +5,13 @@ export const SUBSCRIBEONCE = 'REDUX_ACTION_SUBSCRIBEONCE'; export const UNSUBSCRIBE = 'REDUX_ACTION_UNSUBSCRIBE'; export default () => { - const actionList = []; const subscribe = {}; const subscribeOnce = {}; - function subscribeHandler(subscribe, event, cb, isMultiple = false) { - const _subscribe = subscribe; - if (actionList.indexOf(event) >= 0) { - cb(); - isMultiple ? _subscribe[event] = [cb] : ''; - } else if (_subscribe[event]) { - _subscribe[event].push(cb); + function subscribeHandler(repo, event, cb) { + if (repo[event]) { + repo[event].push(cb); } else { - _subscribe[event] = [cb]; + repo[event] = [cb]; // eslint-disable-line no-param-reassign } } @@ -62,7 +57,6 @@ export default () => { default: } } else { - actionList.push(action.type); if (subscribe[action.type]) { subscribe[action.type].forEach(cb => cb(result)); }