Created
November 29, 2017 05:55
-
-
Save marcusradell/0bdc7e099cc28027d0ebcf066ad74fed to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @NOTE: This is an experimental thought on removing some imperative details from the Redux setup. | |
import { takeLatest, put, call } from 'redux-saga/effects' | |
// @TODO(MANI): Support an apiAction per baseActionType | |
export const makeAsyncMakeSagas = (baseActionType, asyncActionTypes) => { | |
const makeSagas = apiAction => { | |
function* loadSaga(payload) { | |
const result = yield call(apiAction, payload) | |
const { success, data, errors } = result | |
yield success | |
? put({ | |
type: asyncActionTypes[`${baseActionType}_COMPLETED`], | |
payload: data, | |
}) | |
: put({ | |
type: asyncActionTypes[`${baseActionType}_FAILED`], | |
payload: errors, | |
}) | |
} | |
function* watchLoadSaga() { | |
yield takeLatest(baseActionType, loadSaga) | |
} | |
return { | |
loadSaga, | |
watchLoadSaga, | |
} | |
} | |
return makeSagas | |
} | |
export const makeAsyncInitialState = baseInitialState => ({ | |
...baseInitialState, | |
pristine: true, | |
loading: false, | |
errors: null, | |
}) | |
export const loadingUpdater = state => ({ | |
...state, | |
loading: true, | |
errors: null, | |
}) | |
export const failedUpdater = (state, errors) => ({ | |
...state, | |
loading: false, | |
errors, | |
}) | |
export const completedUpdater = (state, successPayloadObject) => ({ | |
...state, | |
pristine: false, | |
loading: false, | |
errors: null, | |
...successPayloadObject, | |
}) | |
export const makeAsyncActionType = actionType => ({ | |
[actionType]: actionType, | |
[`${actionType}_COMPLETED`]: `${actionType}_COMPLETED`, | |
[`${actionType}_FAILED`]: `${actionType}_FAILED`, | |
}) | |
export const makeAsyncUpdater = actionType => ({ | |
[actionType]: loadingUpdater, | |
[`${actionType}_COMPLETED`]: completedUpdater, | |
[`${actionType}_FAILED`]: failedUpdater, | |
}) | |
// @TODO(MANI): Split into two factories? | |
export const makeAsyncActionTypesAndUpdaters = actionTypes => | |
actionTypes.reduce( | |
(acc, actionType) => ({ | |
actionTypes: { | |
...acc.asyncActionTypes, | |
...makeAsyncActionType(actionType), | |
}, | |
updaters: { | |
...acc.asyncUpdaters, | |
...makeAsyncUpdater(actionType), | |
}, | |
}), | |
{ actionTypes: {}, updaters: {} } | |
) | |
export const makeAsyncReducers = (initialState, updaters) => { | |
const reducers = (state = initialState, action) => { | |
const { type, payload } = action | |
return updaters[type] ? updaters[type](state, payload) : state | |
} | |
return reducers | |
} | |
export const makeAsyncModel = (baseInitialState, baseActionTypes) => { | |
const baseActionTypesArray = Array.isArray(baseActionTypes) | |
? baseActionTypes | |
: [baseActionTypes] | |
const initialState = makeAsyncInitialState(baseInitialState) | |
const { actionTypes, updaters } = makeAsyncActionTypesAndUpdaters( | |
baseActionTypesArray | |
) | |
const actionCreators = baseActionTypesArray.reduce( | |
(acc, actionType) => ({ | |
...acc, | |
[actionType | |
.toLowerCase() | |
.replace(/_([a-z])/g, g => g[1].toUpperCase())]: payload => ({ | |
type: actionType, | |
payload, | |
}), | |
}), | |
{} | |
) | |
const reducers = makeAsyncReducers(initialState, updaters) | |
// @TODO(MANI): Make it for all the baseActionTypes in the array | |
const makeSagas = makeAsyncMakeSagas(baseActionTypesArray[0], actionTypes) | |
return { | |
actionTypes, | |
actionCreators, | |
reducers, | |
makeSagas, | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { call } from 'redux-saga/effects' | |
import { makeAsyncModel } from '../container-factory' | |
test('makeAsyncModel', () => { | |
const asyncModel = makeAsyncModel({ value: null }, 'SET_VALUE') | |
expect(asyncModel).toMatchSnapshot() | |
}) | |
test('asyncModel.reducers', () => { | |
const asyncModel = makeAsyncModel({ value: null }, 'SET_VALUE') | |
const expectedInitialState = { | |
value: null, | |
errors: null, | |
loading: false, | |
pristine: true, | |
} | |
const actualInitialState = asyncModel.reducers(undefined, { type: '@@INIT' }) | |
expect(actualInitialState).toEqual(expectedInitialState) | |
}) | |
test('asyncModel.reducers', () => { | |
const asyncModel = makeAsyncModel({ value: null }, 'SET_VALUE') | |
const expectedState = { | |
errors: null, | |
loading: true, | |
pristine: true, | |
value: null, | |
} | |
const initialState = asyncModel.reducers(undefined, { type: '@@INIT' }) | |
const actualState = asyncModel.reducers( | |
initialState, | |
asyncModel.actionCreators.setValue() | |
) | |
expect(actualState).toEqual(expectedState) | |
}) | |
test('asyncModel.makeSagas().loadSaga', () => { | |
const apiActionMock = payload => Promise.resolve(payload) | |
const payloadMock = { id: 'test-id' } | |
const asyncModel = makeAsyncModel({ value: null }, 'SET_VALUE') | |
const { loadSaga } = asyncModel.makeSagas(apiActionMock) | |
const iterator = loadSaga(payloadMock) | |
const result = iterator.next().value | |
expect(result).toEqual(call(apiActionMock, payloadMock)) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment