Skip to content

Commit

Permalink
#30 Avoid store state updates if nothing changed
Browse files Browse the repository at this point in the history
updated POST_STORE_RESOURCE and PATCH_STORE_RESOURCE to detect if no
changes where applied.
  • Loading branch information
remmeier committed Dec 22, 2016
1 parent 99653c7 commit 69470f8
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 63 deletions.
38 changes: 37 additions & 1 deletion spec/reducers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ describe('NgrxJsonApiReducer', () => {
attributes: {
title: 'bla bla bla'
}
}
}
},
}));
expect(newState.data['Article']['1'].resource.attributes.title).toEqual('bla bla bla');
Expand Down Expand Up @@ -357,6 +357,42 @@ describe('NgrxJsonApiReducer', () => {
expect(newState.data['Article']['1']).toBeDefined();
expect(newState2.data['Article']['1']).toBeDefined();
});

it('should not update state on second identical post', () => {
let action = new PostStoreResourceAction(
{ type: 'Article', id: '1', attributes : {title : 'sample title'} }
);
let newState = NgrxJsonApiStoreReducer(state, action);
let newState2 = NgrxJsonApiStoreReducer(newState, action);
expect(newState.data['Article']['1']).toBeDefined();
expect(newState2.data['Article']['1']).toBeDefined();
expect(newState2 === newState).toBeTruthy();
});

it('should not update state on second identical patch', () => {
let action = new PatchStoreResourceAction(
{ type: 'Article', id: '1', attributes : {title : 'sample title', description : 'test description'} }
);
let newState = NgrxJsonApiStoreReducer(state, action);
let newState2 = NgrxJsonApiStoreReducer(newState, action);
expect(newState.data['Article']['1']).toBeDefined();
expect(newState2.data['Article']['1']).toBeDefined();
expect(newState2 === newState).toBeTruthy();
});

it('should not update state on second partial patch', () => {
let newState = NgrxJsonApiStoreReducer(state, new PatchStoreResourceAction(
{ type: 'Article', id: '1', attributes : {title : 'sample title', description : 'sample description'} }
));
let newState2 = NgrxJsonApiStoreReducer(newState, new PatchStoreResourceAction(
{ type: 'Article', id: '1', attributes : {title : 'sample title'} }
));
expect(newState.data['Article']['1'].resource.attributes.title).toEqual("sample title");
expect(newState.data['Article']['1'].resource.attributes.description).toEqual("sample description");
expect(newState2.data['Article']['1'].resource.attributes.title).toEqual("sample title");
expect(newState2.data['Article']['1'].resource.attributes.description).toEqual("sample description");
expect(newState2 === newState).toBeTruthy();
});
});

describe('API_COMMIT_INIT action', () => {
Expand Down
115 changes: 65 additions & 50 deletions src/reducers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as _ from 'lodash';
import { Action, ActionReducer } from '@ngrx/store';

import {
Expand Down Expand Up @@ -38,10 +39,10 @@ export const NgrxJsonApiStoreReducer: ActionReducer<any> =
// console.log("reduce", state, action);

switch (action.type) {
case NgrxJsonApiActionTypes.API_CREATE_INIT:
case NgrxJsonApiActionTypes.API_CREATE_INIT:{
return Object.assign({}, state, { 'isCreating': true });

case NgrxJsonApiActionTypes.API_READ_INIT:
}
case NgrxJsonApiActionTypes.API_READ_INIT:{
newState = Object.assign({}, state, { 'isReading': true });
let query = action.payload.query as ResourceQuery;
// FIXME: handle queries with no queryId
Expand All @@ -51,21 +52,21 @@ export const NgrxJsonApiStoreReducer: ActionReducer<any> =
});
}
return newState;

case NgrxJsonApiActionTypes.REMOVE_QUERY:
}
case NgrxJsonApiActionTypes.REMOVE_QUERY:{
let queryId = action.payload as string;
newState = Object.assign({}, state, {
queries: removeQuery( state.queries, queryId),
});
return newState;

case NgrxJsonApiActionTypes.API_UPDATE_INIT:
}
case NgrxJsonApiActionTypes.API_UPDATE_INIT:{
return Object.assign({}, state, { 'isUpdating': true });

case NgrxJsonApiActionTypes.API_DELETE_INIT:
}
case NgrxJsonApiActionTypes.API_DELETE_INIT:{
return Object.assign({}, state, { 'isDeleting': true });

case NgrxJsonApiActionTypes.API_CREATE_SUCCESS:
}
case NgrxJsonApiActionTypes.API_CREATE_SUCCESS:{
newState = Object.assign({},
state, {
data: updateStoreResources(
Expand All @@ -74,8 +75,8 @@ export const NgrxJsonApiStoreReducer: ActionReducer<any> =
{ 'isCreating': false }
);
return newState;

case NgrxJsonApiActionTypes.API_READ_SUCCESS:
}
case NgrxJsonApiActionTypes.API_READ_SUCCESS:{
newState = Object.assign({},
state, {
data: updateStoreResources(
Expand All @@ -86,8 +87,8 @@ export const NgrxJsonApiStoreReducer: ActionReducer<any> =
{ 'isReading': false }
);
return newState;

case NgrxJsonApiActionTypes.API_UPDATE_SUCCESS:
}
case NgrxJsonApiActionTypes.API_UPDATE_SUCCESS:{
newState = Object.assign(
{},
state, {
Expand All @@ -97,94 +98,108 @@ export const NgrxJsonApiStoreReducer: ActionReducer<any> =
{ 'isUpdating': false }
);
return newState;

case NgrxJsonApiActionTypes.API_DELETE_SUCCESS:
}
case NgrxJsonApiActionTypes.API_DELETE_SUCCESS:{
newState = Object.assign({}, state,
{ data: deleteStoreResources(state.data, action.payload.query) },
{ 'isDeleting': false });
return newState;

case NgrxJsonApiActionTypes.API_CREATE_FAIL:
}
case NgrxJsonApiActionTypes.API_CREATE_FAIL:{
newState = Object.assign({}, state, {
data: updateResourceErrors(state.data, action.payload.query, action.payload.jsonApiData),
'isCreating': false }
);
return newState;

case NgrxJsonApiActionTypes.API_READ_FAIL:
}
case NgrxJsonApiActionTypes.API_READ_FAIL:{
newState = Object.assign({}, state, {
queries: updateQueryErrors(state.queries, action.payload.query.queryId, action.payload.jsonApiData),
'isReading': false
});
return newState;

case NgrxJsonApiActionTypes.API_UPDATE_FAIL:
}
case NgrxJsonApiActionTypes.API_UPDATE_FAIL:{
newState = Object.assign({}, state, {
data: updateResourceErrors(state.data, action.payload.query, action.payload.jsonApiData),
'isUpdating': false }
);
return newState;

case NgrxJsonApiActionTypes.API_DELETE_FAIL:
}
case NgrxJsonApiActionTypes.API_DELETE_FAIL:{
newState = Object.assign({}, state, {
data: updateResourceErrors(state.data, action.payload.query, action.payload.jsonApiData),
'isDeleting': false }
);
return newState;

case NgrxJsonApiActionTypes.QUERY_STORE_SUCCESS:
}
case NgrxJsonApiActionTypes.QUERY_STORE_SUCCESS:{
newState = Object.assign({}, state, {
queries: updateQueryResults(
state.queries,
action.payload.query.queryId,
action.payload.jsonApiData),
})
return newState;

case NgrxJsonApiActionTypes.PATCH_STORE_RESOURCE:
newState = Object.assign({},
state, {
data: updateOrInsertResource(
state.data, action.payload, false, false)
}
);
return newState;
case NgrxJsonApiActionTypes.POST_STORE_RESOURCE:
newState = Object.assign({},
state, {
data: updateOrInsertResource(
state.data, action.payload, false, true)
}
);
return newState;
case NgrxJsonApiActionTypes.DELETE_STORE_RESOURCE:
}
case NgrxJsonApiActionTypes.PATCH_STORE_RESOURCE:{
let updatedData = updateOrInsertResource(state.data, action.payload, false, false);
if(updatedData !== state.data) {
newState = Object.assign({},
state, {
data: updatedData
}
);
return newState;
}else{
return state;
}
}
case NgrxJsonApiActionTypes.POST_STORE_RESOURCE:{
let updatedData = updateOrInsertResource(state.data, action.payload, false, true)
if(updatedData !== state.data) {
newState = Object.assign({},
state, {
data: updatedData
}
);
return newState;
}else{
return state;
}
}
case NgrxJsonApiActionTypes.DELETE_STORE_RESOURCE: {
newState = Object.assign({},
state, {
data: updateResourceState(
state.data, action.payload, ResourceState.DELETED)
}
);
return newState;
case NgrxJsonApiActionTypes.API_COMMIT_INIT:
newState = Object.assign({}, state, { 'isCommitting': true });
}
case NgrxJsonApiActionTypes.API_COMMIT_INIT: {
newState = Object.assign({}, state, {'isCommitting': true});
return newState;
}
case NgrxJsonApiActionTypes.API_COMMIT_SUCCESS:
case NgrxJsonApiActionTypes.API_COMMIT_FAIL:
case NgrxJsonApiActionTypes.API_COMMIT_FAIL: {
// apply all the committed or failed changes
let actions = action.payload as Array<Action>;
newState = state;
for(let commitAction of actions){
for (let commitAction of actions) {
newState = NgrxJsonApiStoreReducer(newState, commitAction);
}
newState = Object.assign({}, newState, {isCommitting: false});
return newState;
case NgrxJsonApiActionTypes.API_ROLLBACK:
}
case NgrxJsonApiActionTypes.API_ROLLBACK:{
newState = Object.assign({},
state, {
data: rollbackStoreResources(state.data)
}
);
return newState;
}
default:
return state;
}
Expand Down
30 changes: 18 additions & 12 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ export const insertStoreResource = (state: NgrxJsonApiStoreResources,
loading : false
};
}

return newState;

return _.isEqual(state, newState) ? state : newState;
};

export const updateStoreResource = (state: NgrxJsonApiStoreResources,
Expand Down Expand Up @@ -224,7 +222,8 @@ export const updateStoreResource = (state: NgrxJsonApiStoreResources,
errors : [],
loading : false
};
return newState;

return _.isEqual(newState[resource.id], state[resource.id]) ? state : newState;
};


Expand Down Expand Up @@ -292,8 +291,6 @@ export const rollbackStoreResources = (state: NgrxJsonApiStoreData): NgrxJsonApi
export const updateOrInsertResource = (state: NgrxJsonApiStoreData,
resource: Resource, fromServer : boolean, override : boolean): NgrxJsonApiStoreData => {

let newState: NgrxJsonApiStoreData = Object.assign({}, state);

// handle relationships first.
// FIXME this is not working, the data section of a relationship contains only <type, id>, not a complete resource
//if (resource.hasOwnProperty('relationships')) {
Expand All @@ -315,18 +312,27 @@ export const updateOrInsertResource = (state: NgrxJsonApiStoreData,

if (_.isUndefined(state[resource.type])) {
// we must mutate the main state (ngrxjsonapistoredata)
let newState: NgrxJsonApiStoreData = Object.assign({}, state);
newState[resource.type] = {};
newState[resource.type] = insertStoreResource(newState[resource.type], resource, fromServer);
return newState;

} else if (_.isUndefined(state[resource.type][resource.id]) || override) {
newState[resource.type] = insertStoreResource(
newState[resource.type], resource, fromServer);
return newState;
let updatedTypeState = insertStoreResource(state[resource.type], resource, fromServer);
if(updatedTypeState !== state[resource.type]) {
let newState: NgrxJsonApiStoreData = Object.assign({}, state);
newState[resource.type] = updatedTypeState;
return newState;
}
return state;
} else {
newState[resource.type] = updateStoreResource(
newState[resource.type], resource, fromServer);
return newState;
let updatedTypeState = updateStoreResource(state[resource.type], resource, fromServer);
if(updatedTypeState !== state[resource.type]){
let newState: NgrxJsonApiStoreData = Object.assign({}, state);
newState[resource.type] = updatedTypeState;
return newState;
}
return state;
}

};
Expand Down

0 comments on commit 69470f8

Please sign in to comment.