From f20c0ddf76940f3fabf121d287ce909bc981b180 Mon Sep 17 00:00:00 2001 From: Mike Stone Date: Thu, 31 May 2018 17:54:58 -0400 Subject: [PATCH] 12 Fix bug deleting entities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit: * Fixes a bug in which entities deleted by the server were not deleted in state. This happened when the server did not respond with the deleted entity’s ID, which is a likely scenario causing a bug in most cases. To delete an entity, dispatch the `destroy` thunk action passing in the entity to delete as the parameter. The entity to delete must have an `id` key. --- src/base_config.js | 32 ++++++++++++++++++++++++++++++++ src/config.js | 2 +- test/config.tests.js | 4 ++-- test/config_reducer.tests.js | 2 +- test/config_thunks.tests.js | 34 +++++++++++++++++++++------------- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/base_config.js b/src/base_config.js index 50bea46..693853c 100644 --- a/src/base_config.js +++ b/src/base_config.js @@ -107,6 +107,8 @@ class BaseConfig { ...this._genericActions(TYPES.DESTROY), ...this._genericActions(TYPES.LOAD), ...this._genericActions(TYPES.UPDATE), + destroy: this._destroyThunkAction(), + silentDestroy: this._destroyThunkAction({ silent: true }), }; } @@ -145,6 +147,36 @@ class BaseConfig { } } + _destroyThunkAction (options = {}) { + const { TYPES } = BaseConfig; + + return (args) => { + return (dispatch) => { + if (!options.silent) { + dispatch(this._genericRequest(TYPES.DESTROY)()); + } + + return this.destroyFunc(args) + .then(() => { + const thunk = this._genericSuccess(TYPES.DESTROY); + const action = thunk(args); + + dispatch(action); + + return args; + }) + .catch((response) => { + const thunk = this._genericFailure(TYPES.DESTROY); + const errorsObject = this.parseServerErrorsFunc(response); + + dispatch(thunk(errorsObject)); + + throw errorsObject; + }); + }; + }; + } + _genericActions (type) { if (!type) { throw new Error('generic action type is not defined'); diff --git a/src/config.js b/src/config.js index f702fb5..1fe969f 100644 --- a/src/config.js +++ b/src/config.js @@ -56,7 +56,7 @@ class ReduxEntityConfig extends BaseConfig { loading: false, errors: {}, data: { - ...entitiesExceptID(state.data, payload.data), + ...entitiesExceptID(state.data, payload.data.id), }, }; } diff --git a/test/config.tests.js b/test/config.tests.js index a09d904..bac632d 100644 --- a/test/config.tests.js +++ b/test/config.tests.js @@ -110,7 +110,7 @@ describe('ReduxEntityConfig - class', () => { createFailure: config._genericFailure('CREATE'), createRequest: config._genericRequest('CREATE'), createSuccess: config._genericSuccess('CREATE'), - destroy: config._genericThunkAction('DESTROY'), + destroy: config._destroyThunkAction('DESTROY'), destroyFailure: config._genericFailure('DESTROY'), destroyRequest: config._genericRequest('DESTROY'), destroySuccess: config._genericSuccess('DESTROY'), @@ -121,7 +121,7 @@ describe('ReduxEntityConfig - class', () => { loadRequest: config._genericRequest('LOAD'), loadSuccess: config._genericSuccess('LOAD'), silentCreate: config._genericThunkAction('CREATE', { silent: true }), - silentDestroy: config._genericThunkAction('DESTROY', { silent: true }), + silentDestroy: config._destroyThunkAction('DESTROY', { silent: true }), silentLoad: config._genericThunkAction('LOAD', { silent: true }), silentLoadAll: config._genericThunkAction('LOAD_ALL', { silent: true }), silentUpdate: config._genericThunkAction('UPDATE', { silent: true }), diff --git a/test/config_reducer.tests.js b/test/config_reducer.tests.js index 80989e5..68f1b0e 100644 --- a/test/config_reducer.tests.js +++ b/test/config_reducer.tests.js @@ -86,7 +86,7 @@ describe('ReduxEntityConfig - reducer', () => { describe('successful action', () => { it('removes the user from state', () => { - const destroySuccessAction = actions.destroySuccess(userStub.id); + const destroySuccessAction = actions.destroySuccess({ id: userStub.id }); const newState = reducer(state, destroySuccessAction); diff --git a/test/config_thunks.tests.js b/test/config_thunks.tests.js index f10b645..60a88a9 100644 --- a/test/config_thunks.tests.js +++ b/test/config_thunks.tests.js @@ -11,18 +11,9 @@ const schemas = { const userStub = stubs.users.valid; const store = { - entities: { - invites: { - errors: {}, - data: {}, - loading: false, - }, - users: { - errors: {}, - data: {}, - loading: false, - }, - }, + errors: {}, + data: {}, + loading: false, }; const standardError = { status: 422, @@ -146,7 +137,10 @@ describe('ReduxEntityConfig - thunks', () => { entityName: 'users', schema: schemas.USERS, }); - const mockStore = reduxMockStore(store); + const mockStore = reduxMockStore({ + ...store, + data: { [userStub.id]: userStub }, + }); it('calls the destroyFunc', () => { const params = { id: 1 }; @@ -168,6 +162,20 @@ describe('ReduxEntityConfig - thunks', () => { expect(dispatchedActionTypes).toNotInclude('users_DESTROY_FAILURE'); }); }); + + it('removes the entity from state', () => { + const promise = config.actions.destroy({ id: userStub.id }); + + return mockStore.dispatch(promise) + .then(() => { + const dispatchedActions = mockStore.getActions(); + const destroySuccessAction = dispatchedActions.find(action => action.type === 'users_DESTROY_SUCCESS'); + const state = mockStore.getState(); + + expect(destroySuccessAction.payload).toEqual({ data: { id: userStub.id } }); + expect(config.reducer(state, destroySuccessAction)).toEqual(store); + }); + }); }); describe('unsuccessful call', () => {