Skip to content

Commit

Permalink
Added abort support to requestsReducer
Browse files Browse the repository at this point in the history
  • Loading branch information
klis87 committed Dec 4, 2017
1 parent 6ceb9d1 commit d0f3fbb
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 34 deletions.
33 changes: 21 additions & 12 deletions packages/redux-saga-requests/src/reducers.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
import { success, error } from './actions';
import { success, error, abort } from './actions';

const getEmptyData = multiple => multiple ? [] : null;

const getRequestState = ({ dataKey, errorKey, fetchingKey, multiple }) => ({
const getInitialRequestState = ({ dataKey, errorKey, pendingKey, multiple }) => ({
[dataKey]: getEmptyData(multiple),
[fetchingKey]: false,
[pendingKey]: 0,
[errorKey]: null,
});

const getInitialState = (state, reducer, config) => {
if (!reducer) {
return getRequestState(config);
return getInitialRequestState(config);
}

return { ...getRequestState(config), ...reducer(undefined, {}) };
return { ...getInitialRequestState(config), ...reducer(undefined, {}) };
};

const defaultConfig = {
getSuccessSuffix: success,
getErrorSuffix: error,
getAbortSuffix: abort,
dataKey: 'data',
errorKey: 'error',
fetchingKey: 'fetching',
pendingKey: 'pending',
multiple: false,
getData: (state, action) => action.payload.data,
onRequest: (state, action, { dataKey, multiple, fetchingKey, errorKey }) => ({
onRequest: (state, action, { dataKey, multiple, pendingKey, errorKey }) => ({
...state,
[dataKey]: getEmptyData(multiple),
[fetchingKey]: true,
[pendingKey]: state[pendingKey] + 1,
[errorKey]: null,
}),
onSuccess: (state, action, { dataKey, fetchingKey, errorKey, getData }) => ({
onSuccess: (state, action, { dataKey, pendingKey, errorKey, getData }) => ({
...state,
[dataKey]: getData(state, action),
[fetchingKey]: false,
[pendingKey]: state[pendingKey] - 1,
[errorKey]: null,
}),
onError: (state, action, { dataKey, multiple, fetchingKey, errorKey }) => ({
onError: (state, action, { dataKey, multiple, pendingKey, errorKey }) => ({
...state,
[dataKey]: getEmptyData(multiple),
[fetchingKey]: false,
[pendingKey]: state[pendingKey] - 1,
[errorKey]: action.payload.error,
}),
onAbort: (state, action, { pendingKey }) => ({
...state,
[pendingKey]: state[pendingKey] - 1,
}),
};

export const createRequestsReducer = (
Expand All @@ -60,8 +65,10 @@ export const createRequestsReducer = (
onRequest,
onSuccess,
onError,
onAbort,
getSuccessSuffix,
getErrorSuffix,
getAbortSuffix,
actionType,
} = config;

Expand All @@ -72,6 +79,8 @@ export const createRequestsReducer = (
return onSuccess(state, action, config);
case getErrorSuffix(actionType):
return onError(state, action, config);
case getAbortSuffix(actionType):
return onAbort(state, action, config);
default:
return reducer ? reducer(nextState, action) : nextState;
}
Expand Down
83 changes: 61 additions & 22 deletions packages/redux-saga-requests/src/reducers.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { success, error, getActionWithSuffix } from './actions';
import { success, error, abort, getActionWithSuffix } from './actions';
import { requestsReducer } from './reducers';

const actionType = 'ACTION';
Expand All @@ -8,7 +8,7 @@ describe('reducers', () => {
describe('without passed reducer', () => {
const defaultState = {
data: null,
fetching: false,
pending: 0,
error: null,
};
const reducer = requestsReducer({ actionType });
Expand All @@ -18,7 +18,7 @@ describe('reducers', () => {
const expected = {
data: null,
error: null,
fetching: false,
pending: 0,
};
assert.deepEqual(state, expected);
});
Expand All @@ -32,7 +32,7 @@ describe('reducers', () => {
const expected = {
data: null,
error: null,
fetching: true,
pending: 1,
};
assert.deepEqual(reducer(defaultState, { type: actionType }), expected);
});
Expand All @@ -42,7 +42,7 @@ describe('reducers', () => {
const expected = {
data,
error: null,
fetching: false,
pending: -1,
};
const action = {
type: success(actionType),
Expand All @@ -56,49 +56,66 @@ describe('reducers', () => {
const expected = {
data: null,
error: someError,
fetching: false,
pending: -1,
};
const action = {
type: error(actionType),
payload: { error: someError },
};
assert.deepEqual(reducer(defaultState, action), expected);
});

it('returns correct state for abort action', () => {
const expected = {
data: null,
error: null,
pending: -1,
};
const action = { type: abort(actionType) };
assert.deepEqual(reducer(defaultState, action), expected);
});
});

describe('without passed reducer with local config override', () => {
const localSuccess = getActionWithSuffix('success');
const localError = getActionWithSuffix('error');
const localAbort = getActionWithSuffix('abort');
const reducer = requestsReducer({
actionType,
getSuccessSuffix: localSuccess,
getErrorSuffix: localError,
getAbortSuffix: localAbort,
dataKey: 'items',
errorKey: 'fail',
fetchingKey: 'pending',
pendingKey: 'fetching',
multiple: true,
getData: (state, action) => ({ nested: action.payload.data }),
onRequest: (state, action, { dataKey, multiple, fetchingKey, errorKey }) => ({
onRequest: (state, action, { dataKey, multiple, pendingKey, errorKey }) => ({
...state,
[dataKey]: multiple ? [] : null,
[fetchingKey]: true,
[pendingKey]: state[pendingKey] + 1,
[errorKey]: null,
multiple,
}),
onSuccess: (state, action, { dataKey, multiple, fetchingKey, errorKey, getData }) => ({
onSuccess: (state, action, { dataKey, multiple, pendingKey, errorKey, getData }) => ({
...state,
[dataKey]: getData(state, action),
[fetchingKey]: false,
[pendingKey]: state[pendingKey] - 1,
[errorKey]: null,
multiple,
}),
onError: (state, action, { dataKey, multiple, fetchingKey, errorKey }) => ({
onError: (state, action, { dataKey, multiple, pendingKey, errorKey }) => ({
...state,
[dataKey]: multiple ? [] : null,
[fetchingKey]: false,
[pendingKey]: state[pendingKey] - 1,
[errorKey]: action.payload.error,
multiple,
}),
onAbort: (state, action, { pendingKey, multiple }) => ({
...state,
[pendingKey]: state[pendingKey] - 1,
multiple,
}),
});
const initialState = reducer(undefined, {});

Expand All @@ -107,7 +124,7 @@ describe('reducers', () => {
const expected = {
items: [],
fail: null,
pending: false,
fetching: 0,
};
assert.deepEqual(state, expected);
});
Expand All @@ -116,7 +133,7 @@ describe('reducers', () => {
const expected = {
items: [],
fail: null,
pending: true,
fetching: 1,
multiple: true,
};
assert.deepEqual(reducer(initialState, { type: actionType }), expected);
Expand All @@ -127,7 +144,7 @@ describe('reducers', () => {
const expected = {
items: { nested: data },
fail: null,
pending: false,
fetching: -1,
multiple: true,
};
const action = {
Expand All @@ -142,7 +159,7 @@ describe('reducers', () => {
const expected = {
items: [],
fail: someError,
pending: false,
fetching: -1,
multiple: true,
};
const action = {
Expand All @@ -151,6 +168,17 @@ describe('reducers', () => {
};
assert.deepEqual(reducer(initialState, action), expected);
});

it('returns correct state for abort action', () => {
const expected = {
items: [],
fail: null,
fetching: -1,
multiple: true,
};
const action = { type: localAbort(actionType) };
assert.deepEqual(reducer(initialState, action), expected);
});
});

describe('with passed reducer', () => {
Expand All @@ -171,7 +199,7 @@ describe('reducers', () => {
const expected = {
data: null,
error: null,
fetching: false,
pending: 0,
counter: 0,
};
assert.deepEqual(state, expected);
Expand All @@ -186,7 +214,7 @@ describe('reducers', () => {
const expected = {
data: null,
error: null,
fetching: true,
pending: 1,
counter: 0,
};
assert.deepEqual(reducer(initialState, { type: actionType }), expected);
Expand All @@ -197,7 +225,7 @@ describe('reducers', () => {
const expected = {
data,
error: null,
fetching: false,
pending: -1,
counter: 0,
};
const action = {
Expand All @@ -212,7 +240,7 @@ describe('reducers', () => {
const expected = {
data: null,
error: someError,
fetching: false,
pending: -1,
counter: 0,
};
const action = {
Expand All @@ -222,11 +250,22 @@ describe('reducers', () => {
assert.deepEqual(reducer(initialState, action), expected);
});

it('returns correct state for abort action', () => {
const expected = {
data: null,
error: null,
pending: -1,
counter: 0,
};
const action = { type: abort(actionType) };
assert.deepEqual(reducer(initialState, action), expected);
});

it('handles action type from passed reducer', () => {
const expected = {
data: null,
error: null,
fetching: false,
pending: 0,
counter: 1,
};
const action = { type: INCREMENT };
Expand Down

0 comments on commit d0f3fbb

Please sign in to comment.