{"id":20201371,"url":"https://github.com/voronar/redux-multireducer","last_synced_at":"2025-04-10T11:24:20.800Z","repository":{"id":88301644,"uuid":"83725540","full_name":"Voronar/redux-multireducer","owner":"Voronar","description":"Reusable Reducer Logic","archived":false,"fork":false,"pushed_at":"2017-10-13T07:45:08.000Z","size":85,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T10:11:49.283Z","etag":null,"topics":["ecmascript2015","react","redux"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Voronar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-02T21:15:33.000Z","updated_at":"2019-11-11T15:54:23.000Z","dependencies_parsed_at":"2023-12-16T21:18:50.451Z","dependency_job_id":null,"html_url":"https://github.com/Voronar/redux-multireducer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronar%2Fredux-multireducer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronar%2Fredux-multireducer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronar%2Fredux-multireducer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Voronar%2Fredux-multireducer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Voronar","download_url":"https://codeload.github.com/Voronar/redux-multireducer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248208612,"owners_count":21065203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecmascript2015","react","redux"],"created_at":"2024-11-14T04:50:50.706Z","updated_at":"2025-04-10T11:24:20.793Z","avatar_url":"https://github.com/Voronar.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redux multireducer concept ([RU](https://github.com/Voronar/redux-multireducer/blob/master/README.ru.md))\n## The problem\nWhen we want to reuse our single reducer function for multiple reducer instances we face a problem. Redux [creator](https://github.com/gaearon) write:\n\u003e As an example, let's say that we want to track multiple counters in our application, named A, B, and C. We define our initial ```counter``` reducer, and we use ```combineReducers``` to set up our state:\n\n```javascript\nfunction counter(state = 0, action) {\n    switch (action.type) {\n        case 'INCREMENT':\n            return state + 1;\n        case 'DECREMENT':\n            return state - 1;\n        default:\n            return state;\n    }\n}\n\nconst rootReducer = combineReducers({\n    counterA : counter,\n    counterB : counter,\n    counterC : counter\n});\n```\n\n\u003eUnfortunately, this setup has a problem. Because ```combineReducers``` will call each slice reducer with the same action, dispatching ```{type : 'INCREMENT'}``` will actually cause all three counter values to be incremented, not just one of them.\n\n## The solution\n\n#### To solve this problem we need a ```specific``` action types for the specialized version of our reducer function.\n\n## FP solution\n[Dan](https://github.com/gaearon) offers a [solution](http://redux.js.org/docs/recipes/reducers/ReusingReducerLogic.html#customizing-behavior-with-higher-order-reducers) from a world of *functional programming* - higher-order reducer. He wraps the reducer with a higher-order function (*_HOF_*) and specify an action type this suffix/prefix passing it from *_HOF_*. The same approach (he specifies action object with special ```meta-key```) use [Erik Rasmussen](https://github.com/erikras) in his [library](https://github.com/erikras/multireducer).\n\n## OOP solution\nI approach more or less the same solution but without wrappers, suffixes/prefixes, meta-keys, etc. I highlighted ```specific``` word in a solution section not without a reason. What if we do an action type **REALY** unique? Greeting, [```Symbol```](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol). From MDN:\n\u003eEvery symbol value returned from Symbol() is unique.  A symbol value may be used as an identifier for object properties; this is the data type's only purpose.\n\nPerfect choice, is not it? And why we need *object-oriented programming*? OOP help us to optimize the code organization and make our action types unique. Redux ingredients (or *Redux module*) organization (reducer, constants, action creators) was inspired by a [modular redux](https://github.com/erikras/ducks-modular-redux) approach from all the same developer [Erik Rasmussen](https://github.com/erikras).\nLet's try the approach in a list view React application example (working example included in this repository, just clone it, ```npm i``` and ```npm run start```).\n\n\u003e ️️ **NOTICE** ️️ ```Symbol``` constants impose some restrictions for  several of Redux's defining features, such as time travel debugging, and recording and replaying actions. More information read [there](http://redux.js.org/docs/faq/Actions.html#actions-string-constants).\n\u003e 😉 But this problem is [simple to resolve](https://github.com/Voronar/redux-multireducer/blob/master/src/redux/modules/list/List.devtools.ready.js).\n\n## Example (list view React application)\n### Redux ```list``` module\nRedux ```list``` module is a directory that includes redux module class and required module instances.\n\n#### ```src/redux/modules/list/List.js``` - Redux list module class\n\n```javascript\nimport * as services from './../../../api/services';\n\nconst initialState = {\n  list: [],\n};\n\nfunction getListReducer(state, action) {\n  return {\n    ...state,\n    list: action.payload.list,\n  };\n}\n\nfunction removeItemReducer(state, action) {\n  const { payload } = action;\n  const list = state.list.filter((item, i) =\u003e i !== payload.index);\n  return {\n    ...state,\n    list,\n  };\n}\n\nexport default class List {\n  constructor() {\n    // action types constants\n    this.GET_LIST = Symbol('GET_LIST');\n    this.REMOVE_ITEM = Symbol('REMOVE_ITEM');\n  }\n  getList = (serviceName) =\u003e {\n    return async (dispatch) =\u003e {\n      const list = await services[serviceName].get();\n      dispatch({\n        type: this.GET_LIST,\n        payload: {\n          list,\n          serviceName,\n        },\n      });\n    };\n  }\n  removeItem = (index) =\u003e {\n    return (dispatch) =\u003e {\n      dispatch({\n        type: this.REMOVE_ITEM,\n        payload: {\n          index,\n        },\n      });\n    };\n  }\n  reducer = (state = initialState, action) =\u003e {\n    switch (action.type) {\n      case this.GET_LIST:\n        return getListReducer(state, action);\n\n      case this.REMOVE_ITEM:\n        return removeItemReducer(state, action);\n\n      default:\n        return state;\n    }\n  }\n}\n```\n\n\u003e⚠️️ **IMPORTANT** ⚠️️ Action creators and reducer must be class instance methods, not prototype methods otherwise you will loose your ```this```.\n\n#### ```src/redux/modules/list/index.js``` - Redux module instances\n\n```javascript\n// Redux list module class\nimport List from './List';\n\nexport default {\n  users: new List(),\n  posts: new List(),\n};\n```\nJust create Redux module class and reuse it making so many instances as we need.\n\n#### ```src/redux/modules/reducer.js``` - main reducer\n\n```javascript\nimport { combineReducers } from 'redux';\n\n// required Redux module instances\nimport list from './list/index';\n\nexport default combineReducers({\n  users: list.users.reducer,\n  posts: list.posts.reducer,\n});\n```\n\n#### ```src/components/ListView.js``` - React list view component\n\n```javascript\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { bindActionCreators } from \"redux\";\n\n// Redux module instances\nimport list from './../redux/modules/list';\n\nclass ListView extends React.Component {\n  componentWillMount() {\n    this.props.getList(this.props.serviceName);\n  }\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003ch1\u003e{this.props.serviceName}\u003c/h1\u003e\n        \u003cul\u003e\n          {this.props.list.map((item, i) =\u003e\n            \u003cspan key={i}\u003e\n              \u003cli style={{ width: 100 }}\u003e\n                {item}\n                \u003cbutton style={{ float: 'right' }} onClick={() =\u003e this.props.removeItem(i)}\u003ex\u003c/button\u003e\n              \u003c/li\u003e\n\n            \u003c/span\u003e)\n          }\n        \u003c/ul\u003e\n        \u003cbutton onClick={() =\u003e this.props.getList(this.props.serviceName)}\u003eUpdate\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nconst mapStateToProps = (state, { serviceName }) =\u003e ({\n  ...state[serviceName],\n});\n\nconst mapDispatchToProps = (dispatch, { serviceName }) =\u003e ({\n  ...bindActionCreators({ ...list[serviceName]}, dispatch),\n});\n\nexport default connect(mapStateToProps, mapDispatchToProps)(ListView);\n```\n\n#### ```src/App.jsx``` - React list view component using\n\n```javascript\nimport React, { Component } from 'react';\nimport logo from './logo.svg';\nimport './App.css';\nimport ListView from './components/ListView';\n\nclass App extends Component {\n  render() {\n    return (\n      \u003cdiv className=\"App\"\u003e\n        \u003cdiv className=\"App-header\"\u003e\n          \u003cimg src={logo} className=\"App-logo\" alt=\"logo\" /\u003e\n          \u003ch2\u003eTry to Redux multireducer\u003c/h2\u003e\n        \u003c/div\u003e\n        \u003cListView serviceName=\"users\" /\u003e\n        \u003cListView serviceName=\"posts\" /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nexport default App;\n```\n\n## Conclusion\n\nThis way using modern JavaScript you can do  your Redux module more reusable. I will be glad to listen your suggestions and critique in repository [Issue](https://github.com/Voronar/redux-multireducer/issues) section.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronar%2Fredux-multireducer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoronar%2Fredux-multireducer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoronar%2Fredux-multireducer/lists"}