📜 ⬆️ ⬇️

Modern javascript or how to make your redux module ready for reuse



If we want to reuse our reducer function for several instances of the final reducer, then we are faced with a problem.



Redux Multi-Receiver Concept ( EN )


Problem


Redux creator writes about this:


For example, we combineReducers to combineReducers our state of combineReducers :

 function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } const rootReducer = combineReducers({ counterA : counter, counterB : counter, counterC : counter }); 

Unfortunately, this setup has a problem. combineReducers will be the combineReducers will combineReducers .

Decision


To solve this problem, we need types of actions for a certain version of our function reducer.


AF solution


Dan offers a solution from the world of functional programming - a higher order reducer. He wraps the reducer with a higher order function ( FVP ) and names the type of action with an additional suffix / prefix that is forwarded through the FVP . A similar approach (he specializes an action object using a special - ) uses Erik Rasmussen in his library .


OOP solution


I suggest a more or less similar approach, but without wrappers, suffixes / prefixes, meta keys, and the like. In the solution section, I highlighted the word not without reason. What if we take and make the type of action REALLY unique? Meet the Symbol . MDN clipping:


Every symbol value returned from Symbol () is unique. A symbol value may be used as an identifier for object properties; this is the data type only purpose.

Ideal, is not it? And where does object-oriented programming ? OOP allows you to organize our code in the most optimal way and make our types of action games unique. The way the organization of the Redux-ingredients (or Redux-module) was inspired by the modular Redux of the same Erik Rasmussen .
Let's try to apply this approach using the example of a React application to display lists (a working example with integration with redux devtools chrome extension is in the repository of this documentation, just copy the repository, and run a couple of npm i and npm run start commands).


️️ WARNING ️️ Symbol constants impose some restrictions on Redux features such as time-shifted debugging, action recording and playback. Read more here . But this problem is easily solved .

Example (React application for displaying lists)


Redux list module


Redux list module - directory in which the Redux-class of the module and the required instances of this module are located.


src/redux/modules/list/List.js - Redux-class of the list module


src / redux / modules / list / List.js
 import * as services from './../../../api/services'; const initialState = { list: [], }; function getListReducer(state, action) { return { ...state, list: action.payload.list, }; } function removeItemReducer(state, action) { const { payload } = action; const list = state.list.filter((item, i) => i !== payload.index); return { ...state, list, }; } export default class List { constructor() { // action types constants this.GET_LIST = Symbol('GET_LIST'); this.REMOVE_ITEM = Symbol('REMOVE_ITEM'); } getList = (serviceName) => { return async (dispatch) => { const list = await services[serviceName].get(); dispatch({ type: this.GET_LIST, payload: { list, serviceName, }, }); }; } removeItem = (index) => { return (dispatch) => { dispatch({ type: this.REMOVE_ITEM, payload: { index, }, }); }; } reducer = (state = initialState, action) => { switch (action.type) { case this.GET_LIST: return getListReducer(state, action); case this.REMOVE_ITEM: return removeItemReducer(state, action); default: return state; } } } 

src / redux / modules / list / List.devtools.ready.js
 import * as services from './../../../api/services'; const initialState = { list: [], }; function getListReducer(state, action) { return { ...state, list: action.payload.list, }; } function removeItemReducer(state, action) { const { payload } = action; const list = state.list.filter((item, i) => i !== payload.index); return { ...state, list, }; } function actionType(name) { return { type: name, metaType: Symbol(name), }; } export default class List { constructor(prefix) { this.GET_LIST = actionType(`${prefix}/GET_LIST`); this.REMOVE_ITEM = actionType(`${prefix}/REMOVE_ITEM`); } getList = (serviceName) => { return async (dispatch) => { const list = await services[serviceName].get(); const { type, metaType } = this.GET_LIST; dispatch({ payload: { list, serviceName, }, type, metaType, }); }; } removeItem = (index) => { return (dispatch) => { const { type, metaType } = this.REMOVE_ITEM; dispatch({ payload: { index, }, type, metaType, }); }; } reducer = (state = initialState, action) => { switch (action.metaType) { case this.GET_LIST.metaType: return getListReducer(state, action); case this.REMOVE_ITEM.metaType: return removeItemReducer(state, action); default: return state; } } } 

️️ IMPORTANT Action generators and reducer should be methods of the class instance, not its prototype, otherwise you will lose this .

src/redux/modules/list/index.js - Redux module instances


 // Redux list module class import List from './List'; export default { users: new List(), posts: new List(), }; 

Just create a class of Redux-module and reuse it, make as many instances as necessary.


src/redux/modules/reducer.js - Master Reducer


 import { combineReducers } from 'redux'; // required Redux module instances import list from './list/index'; export default combineReducers({ users: list.users.reducer, posts: list.posts.reducer, }); 

src/components/ListView.js - React-component for displaying lists


src / components / ListView.js
 import * as React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from "redux"; // Redux module instances import list from './../redux/modules/list'; class ListView extends React.Component { componentWillMount() { this.props.getList(this.props.serviceName); } render() { return ( <div> <h1>{this.props.serviceName}</h1> <ul> {this.props.list.map((item, i) => <span key={i}> <li style={{ width: 100 }}> {item} <button style={{ float: 'right' }} onClick={() => this.props.removeItem(i)}>x</button> </li> </span>) } </ul> <button onClick={() => this.props.getList(this.props.serviceName)}>Update</button> </div> ); } } const mapStateToProps = (state, { serviceName }) => ({ ...state[serviceName], }); const mapDispatchToProps = (dispatch, { serviceName }) => ({ ...bindActionCreators({ ...list[serviceName]}, dispatch), }); export default connect(mapStateToProps, mapDispatchToProps)(ListView); 

src/App.jsx - Using a React Component to Display Lists


src / App.jsx
 import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; import ListView from './components/ListView'; class App extends Component { render() { return ( <div className="App"> <div className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h2>Try to Redux multireducer</h2> </div> <ListView serviceName="users" /> <ListView serviceName="posts" /> </div> ); } } export default App; 

Conclusion


Thus, using modern JavaScript you can make your Redux-module more convenient for reuse. I would be glad to hear criticism and suggestions in the Issue section of the repository of this documentation.


')

Source: https://habr.com/ru/post/323164/


All Articles