Use keyed redux record store

It's the recommended approach in Redux docs, and more performant (key-based).
It'll also simplify a move to Immutable.js later on.

The PHP APIs still return unkeyed data, which is standard REST API behaviour,
but for Redux state we transform it to be keyed.
This commit is contained in:
Ingo Schommer 2016-04-13 10:12:14 +12:00 committed by Damian Mooyman
parent f7237a9936
commit aefc1a5c01
4 changed files with 57 additions and 33 deletions

View File

@ -56,7 +56,8 @@ class GridField extends SilverStripeComponent {
); );
const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>; const header = <GridFieldHeader>{headerCells.concat(actionPlaceholder)}</GridFieldHeader>;
const rows = records.map((record, i) => { const rows = Object.keys(records).map((i) => {
const record = records[i];
// Build cells // Build cells
const cells = columns.map((column, j) => { const cells = columns.map((column, j) => {
// Get value by dot notation // Get value by dot notation
@ -141,7 +142,7 @@ GridField.propTypes = {
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
const recordType = ownProps.data ? ownProps.data.recordType : null; const recordType = ownProps.data ? ownProps.data.recordType : null;
return { return {
records: (state.records && recordType) ? state.records[recordType] : [], records: (state.records && recordType) ? state.records[recordType] : {},
}; };
} }

View File

@ -218,9 +218,7 @@ function mapStateToProps(state, ownProps) {
// Find record specific to this item // Find record specific to this item
let record = null; let record = null;
if (state.records && state.records.ChangeSet && ownProps.campaignId) { if (state.records && state.records.ChangeSet && ownProps.campaignId) {
record = state.records.ChangeSet.find( record = state.records.ChangeSet[parseInt(ownProps.campaignId, 10)];
(nextRecord) => (nextRecord.ID === parseInt(ownProps.campaignId, 10))
);
} }
return { return {
record: record || [], record: record || [],

View File

@ -7,7 +7,6 @@ function recordsReducer(state = initialState, action) {
let records; let records;
let recordType; let recordType;
let record; let record;
let recordIndex;
switch (action.type) { switch (action.type) {
@ -29,7 +28,8 @@ function recordsReducer(state = initialState, action) {
case ACTION_TYPES.FETCH_RECORDS_SUCCESS: case ACTION_TYPES.FETCH_RECORDS_SUCCESS:
recordType = action.payload.recordType; recordType = action.payload.recordType;
// TODO Automatic pluralisation from recordType // TODO Automatic pluralisation from recordType
records = action.payload.data._embedded[`${recordType}s`]; records = action.payload.data._embedded[`${recordType}s`] || [];
records = records.reduce((prev, val) => Object.assign({}, prev, { [val.id]: val }), {});
return deepFreeze(Object.assign({}, state, { return deepFreeze(Object.assign({}, state, {
[recordType]: records, [recordType]: records,
})); }));
@ -43,18 +43,9 @@ function recordsReducer(state = initialState, action) {
case ACTION_TYPES.FETCH_RECORD_SUCCESS: case ACTION_TYPES.FETCH_RECORD_SUCCESS:
recordType = action.payload.recordType; recordType = action.payload.recordType;
record = action.payload.data; record = action.payload.data;
records = state[recordType] ? state[recordType] : [];
// Update or insert
recordIndex = records.findIndex((nextRecord) => (nextRecord.ID === record.ID));
if (recordIndex > -1) {
records[recordIndex] = record;
} else {
records.push(record);
}
return deepFreeze(Object.assign({}, state, { return deepFreeze(Object.assign({}, state, {
[recordType]: records, [recordType]: Object.assign({}, state[recordType], { [record.ID]: record }),
})); }));
case ACTION_TYPES.DELETE_RECORD_REQUEST: case ACTION_TYPES.DELETE_RECORD_REQUEST:
@ -65,8 +56,14 @@ function recordsReducer(state = initialState, action) {
case ACTION_TYPES.DELETE_RECORD_SUCCESS: case ACTION_TYPES.DELETE_RECORD_SUCCESS:
recordType = action.payload.recordType; recordType = action.payload.recordType;
records = state[recordType] records = state[recordType];
.filter(nextRecord => nextRecord.ID !== action.payload.id); records = Object.keys(records)
.reduce((result, key) => {
if (parseInt(key, 10) !== parseInt(action.payload.id, 10)) {
return Object.assign({}, result, { [key]: records[key] });
}
return result;
}, {});
return deepFreeze(Object.assign({}, state, { return deepFreeze(Object.assign({}, state, {
[recordType]: records, [recordType]: records,

View File

@ -8,29 +8,57 @@ const recordsReducer = require('../reducer').default;
const ACTION_TYPES = require('../action-types').default; const ACTION_TYPES = require('../action-types').default;
describe('recordsReducer', () => { describe('recordsReducer', () => {
describe('FETCH_RECORD_SUCCESS', () => {
it('adds a new record', () => {
const initialState = {
TypeA: {
11: { ID: 11 },
},
TypeB: {
11: { ID: 11 },
},
};
const nextState = recordsReducer(initialState, {
type: ACTION_TYPES.FETCH_RECORD_SUCCESS,
payload: { recordType: 'TypeA', data: { ID: 12 } },
});
expect(nextState.TypeA).toEqual({
11: { ID: 11 },
12: { ID: 12 },
});
expect(nextState.TypeB).toEqual({
11: { ID: 11 },
});
});
});
describe('DELETE_RECORD_SUCCESS', () => { describe('DELETE_RECORD_SUCCESS', () => {
const initialState = { const initialState = {
TypeA: [ TypeA: {
{ ID: 1 }, 11: { ID: 11 },
{ ID: 2 }, 12: { ID: 12 },
], },
TypeB: [ TypeB: {
{ ID: 1 }, 11: { ID: 11 },
{ ID: 2 }, 12: { ID: 12 },
], },
}; };
it('removes records from the declared type', () => { it('removes records from the declared type', () => {
const nextState = recordsReducer(initialState, { const nextState = recordsReducer(initialState, {
type: ACTION_TYPES.DELETE_RECORD_SUCCESS, type: ACTION_TYPES.DELETE_RECORD_SUCCESS,
payload: { recordType: 'TypeA', id: 2 }, payload: { recordType: 'TypeA', id: 12 },
}); });
expect(nextState.TypeA.length).toBe(1); expect(nextState.TypeA).toEqual({
expect(nextState.TypeA[0].ID).toBe(1); 11: { ID: 11 },
expect(nextState.TypeB.length).toBe(2); });
expect(nextState.TypeB[0].ID).toBe(1); expect(nextState.TypeB).toEqual({
expect(nextState.TypeB[1].ID).toBe(2); 11: { ID: 11 },
12: { ID: 12 },
});
}); });
}); });
}); });