Redux Methods
No more boilerplate for common actions. Define your initial state and common actions once, then access the actions anywhere in one object.
npm install redux-methods
OR
yarn add redux-methods
Getting Started
Define your initial state
// initialState.js const initialState = ui: isOnline: true isSidebarOpen: true errorMessage: '' inProgress: isGettingProfile: false user: profile: {} ;
OR
// ui.js const ui = isOnline: true isSidebarOpen: true errorMessage: ''; //... similar for inProgress.js, user.js
//initialState.js ;;;
// in store.js ; //... etc
Define your action methods
Methods should be defined in camel case, and not conflict with any properties in your initial state. These methods will be your action types which can be dispatched on any property in your initial state.
// methods.js /** * @description - each action method is called with the below, and must return * the new value for the slice * @param * @param * method creator. Mutation should be avoided. * @param * action method creator. Mutation should be avoided. */ const setValue = payload;const increment = slice + payload;const decrement = slice - payload;const addById = ...slice payloadid: payload ;const reset = initial;
Optional - Define other methods
You can define additional methods such as thunks, selectors, or other simple actions that you don't want shared with all properties as an object of functions. These will all be accessible in the methods object at their respective path. Each object of additional methods will be merged in to the methods object.
// thunks.js const fetchProfile = ;//async action here // selectors.js const selectProfile = stateuserprofile; //selector here
Initialize the enhancer
Pass in your defined methods, and any additional objects containing functions you want to have access to in the methods object.
// store.js ;; // or import * as initialState;;;; /** * @description - methodsEnhancer initialises redux-methods * @param {{}} methods - The methods you have defined which will become your * action types available at every property defined in your initialState. * @param {{}} ...additionalMethods - additional arguments should be objects * containing additional methods, eg thunks, selectors, or other action creators. */ const store = ; ;
Optional - using with namespaced reducers
If you are using namespaced reducers (for example, using combineReducers) the initialState created by redux-methods will cause an error (unexpected keys) - to work around this, spread the createPassthroughReducers function with the initialState as its argument to create a reducer for each namespace in your defined initialState that simply returns the state.
// reducer.js ;;; const reducer = ; ;
Usage
// someContainer.js ;;; const mapStateToProps = profile: methods; const mapDispatchToProps = fetchProfile: methodsfetchProfile; mapStateToProps mapDispatchToPropssomeComponent; // thunks.js ; const fetchProfile = { const inProgress: isGettingProfile ui: errorMessage user: profile = methods; ; return axios ;};
Advanced Usage
Two methods are provided at the root level of the methods object - tutti and custom. See below for the previous example rewritten using these methods.
methods.tutti
- this method allows you to perform multiple actions in a single dispatch.
By using this method you will need to look at the action itself in Redux Dev Tools to see which actions were performed.
methods.custom
- this method allows you to perform any of your defined method actions on a path that you were unable to define in your initialState. It will create the path if it doesn't exist.
// someContainer.js ;;; const mapStateToProps = profile: methods; const mapDispatchToProps = fetchProfile: methodsfetchProfile updateName: methodscustom; mapStateToProps mapDispatchToPropssomeComponent; // thunks.js ; const fetchProfile = { const inProgress: isGettingProfile tutti ui: errorMessage user: profile = methods; ; return axios ;};
Explanation
The methodsEnhancer function does two things.
Firstly, it creates an object with the same deep properties as the provided initialState, excluding final values. Each property contains an action creator for each of the provided methods.
Example
const initialState = ui: inProgress: isGettingProfile: false isOnline: true ;const methods = slice + payload initial payload; // using the above initialState and methods passed to methodsEnhancer, the// imported methods object would be as below: methods = custom: path payload type: '@@redux-methods/INCREMENT' path payload type: '@@redux-methods/RESET' path payload type: '@@redux-methods/SET_VALUE' payload type: '@@redux-methods/TUTTI' ui: path: 'ui' payload type: '@@redux-methods/INCREMENT' path: 'ui' payload type: '@@redux-methods/RESET' path: 'ui' payload type: '@@redux-methods/SET_VALUE' inProgress: path: 'ui.inProgress' payload type: '@@redux-methods/INCREMENT' path: 'ui.inProgress' payload type: '@@redux-methods/RESET' path: 'ui.inProgress' payload type: '@@redux-methods/SET_VALUE' isGettingProfile: path: 'ui.inProgress.isGettingProfile' payload type: '@@redux-methods/INCREMENT' path: 'ui.inProgress.isGettingProfile' payload type: '@@redux-methods/RESET' path: 'ui.inProgress.isGettingProfile' payload type: '@@redux-methods/SET_VALUE' isOnline: path: 'ui.isOnline' payload type: '@@redux-methods/INCREMENT' path: 'ui.isOnline' payload type: '@@redux-methods/RESET' path: 'ui.isOnline' payload type: '@@redux-methods/SET_VALUE' ;
Secondly, it creates a root-level reducer. This reducer checks if the action type is a defined method. If it isn't, it passes the action on to the default reducer.
Otherwise, it reduces down to the slice of state (and initial state) determined by the path value, then passes those slices and payload to the defined method which matches the action type.
TODO
- Check for and disallow duplicate paths
- Clean up readme