ReduxActionHandlers
ActionHandlers library to improve the Redux development experience
How to use this:
- Put all Your reducer cases into functions, and put them in the action file
src/actions/downloadSomething.js
{ return ...state registration: actionpayload isFetching: true data: {} }
- Add them to actionHandlers (for reducer "something", map "DOWNLOAD_SOMETHING_REQUEST" action type to "downloadSomethingRequest_actionHandler" function)
actionHandlers
- Add handling to reducer
const reducer = { return actionHandlers;}
Benefits of using this library:
- One file describing all the functionality of an action. Instead of splitting the action creator logic and reducer logic to two separate files, one concern is in one file.
- Huge reducers are no longer a problem. Instead of having all the reducer handling code in one huge switch...case block, each action is in it's own function which makes it cleaner.
- When using combineReducers, You create shards. Those shards are enforced in actionHandlers. This means that You will never have a polluted root reducer namespace
Compatibility:
- This library has no dependencies
- It does not force the ActionHandlers on Your whole project. You can still use both classic reducer switch...case and ActionHandlers.
- It does not pollute the redux state with functions.
- It's a clean library - it supports all use cases.
- Supports nested combineReducers
- Supports many-to-many relationships between actions and reducer cases.
- It's tiny. All it takes is 31 lines of code.
- Testing is unchanged, but can be optimized. By testing the reducer functions directly, tests can become much clearer, but this library doesn't force it. You can test the whole reducer.
ReduxActionHandlers reason to exist
To create a single action and a reducer, you have to create multiple files and put a part of the implementation in each of them:
src/actions/downloadSomething_types.js
const DOWNLOAD_SOMETHING_REQUEST = '@request/DOWNLOAD_SOMETHING_REQUEST'const DOWNLOAD_SOMETHING_SUCCESS = '@request/DOWNLOAD_SOMETHING_SUCCESS'const DOWNLOAD_SOMETHING_FAILURE = '@request/DOWNLOAD_SOMETHING_FAILURE'
src/actions/downloadSomething.js
{ return { return api }}
src/reducers/downloadReducer.js
const initialState = isFetching: false data: {} registration: null visible: false; const reducer = { if !actiontype return state }
This single concern, single chunk of functionality is spread over three separate files.
It makes it difficult to reason about and debug. The reducer construction is also an issue because it very easily can grow into a monster like this (and this has a potential to be much bigger):
const defaultState = data: {} flightId: null isFetching: false isUpdating: false visible: false { if !actiontype return state }
Solution:
ReduxActionHandlers!
Idea:
Put all the reducer cases into functions like so:
Before:
case DOWNLOAD_SOMETHING_REQUEST: return ...state registration: actionpayload isFetching: true data: {}
After:
{ return ...state registration: actionpayload isFetching: true data: {} }
Then, instead of attaching actions externally ( from reducer ) we would attach actions to a reducer like so:
actionHandlers
Resulting files would be as follows:
src/actions/downloadSomething.js
const DOWNLOAD_SOMETHING_REQUEST = '@request/DOWNLOAD_SOMETHING_REQUEST'const DOWNLOAD_SOMETHING_SUCCESS = '@request/DOWNLOAD_SOMETHING_SUCCESS'const DOWNLOAD_SOMETHING_FAILURE = '@request/DOWNLOAD_SOMETHING_FAILURE' { return ...state registration: actionpayload isFetching: true data: {} } { return ...state data: actionpayload isFetching: false } { return ...state data: {} isFetching: false } { return { return api }} actionHandlersactionHandlersactionHandlers
src/reducers/downloadSomethingReducer.js
const initialState = isFetching: false data: {} registration: null visible: false; const reducer = { return actionHandlers;}