{"action":"opened","number":1904,"pull_request":{"url":"https://api.github.com/repos/reactjs/redux/pulls/1904","id":82099407,"html_url":"https://github.com/reactjs/redux/pull/1904","diff_url":"https://github.com/reactjs/redux/pull/1904.diff","patch_url":"https://github.com/reactjs/redux/pull/1904.patch","issue_url":"https://api.github.com/repos/reactjs/redux/issues/1904","number":1904,"state":"open","locked":false,"title":"[Proposal] Make `combineReducers` pass extra arguments to child reducers","user":{"login":"aikoven","id":857709,"avatar_url":"https://avatars.githubusercontent.com/u/857709?v=3","gravatar_id":"","url":"https://api.github.com/users/aikoven","html_url":"https://github.com/aikoven","followers_url":"https://api.github.com/users/aikoven/followers","following_url":"https://api.github.com/users/aikoven/following{/other_user}","gists_url":"https://api.github.com/users/aikoven/gists{/gist_id}","starred_url":"https://api.github.com/users/aikoven/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aikoven/subscriptions","organizations_url":"https://api.github.com/users/aikoven/orgs","repos_url":"https://api.github.com/users/aikoven/repos","events_url":"https://api.github.com/users/aikoven/events{/privacy}","received_events_url":"https://api.github.com/users/aikoven/received_events","type":"User","site_admin":false},"body":"This can be useful a couple cases:\r\n\r\n## 1. Reducers with dependencies\r\n\r\nIn my opinion the most explicit and convenient way to have one branch of reducers dependent on another branch is to make them have common parent that calculates dependency branch first and then passes its to the dependent reducer:\r\n\r\n```js\r\nconst dependencyReducer = (state = ..., action) => {...};\r\n\r\nconst dependentReducer = (state = ..., action, dependency) => {...};\r\n\r\nconst parentReducer = (state = {}, action) => {\r\n  const {dependency, dependent} = state;\r\n\r\n  const nextDependency = dependencyReducer(state, action);\r\n  const nextDependent = dependentReducer(state, action, nextDependency);\r\n\r\n  if (nextDependency !== dependency || nextDependent !== dependent)\r\n    return {dependency: nextDependency, dependent: nextDependent};\r\n\r\n  return state;\r\n};\r\n```\r\n\r\nThe change in this PR allows `dependentReducer` to be built from smaller parts, each one accepting dependency.\r\n\r\n## 2. Multiple branches of same shape\r\n\r\nSuppose we are developing an instant messenger. We can switch between chat views for different contacts and we want to store some data for each view, e.g. entered text, scroll position etc.\r\n\r\nLet's structure state like this:\r\n\r\n```js\r\n{\r\n  alice: {\r\n    enteredText: '...',\r\n    scrollPosition: ...,\r\n  },\r\n  bob: {\r\n    enteredText: '...',\r\n    scrollPosition: ...,\r\n  },\r\n  ...\r\n}\r\n```\r\n\r\nActions:\r\n\r\n```js\r\nconst chatOpened = (contact) => ({\r\n  type: 'CHAT_OPENED',\r\n  contact,\r\n});\r\n\r\nconst textEntered = (contact, text) => ({\r\n  type: 'TEXT_ENTERED',\r\n  contact,\r\n  text,\r\n});\r\n```\r\n\r\nMy first approach to this was to have parent reducer look into action's `contact` prop and pass action to `chatViewReducer` for particular branch. But that couples parent to child, parent has to know of all actions that child accepts etc.\r\n\r\nSo my current approach is to pass all actions to `chatViewReducer` for all child branches with `contact` passed as third argument:\r\n\r\n```js\r\nconst chatsReducer = (state = {}, action) => {\r\n  if (action.type === 'CHAT_OPENED') {\r\n    return {\r\n      ...state, \r\n      [action.contact]: chatViewReducer(undefined, action, action.contact),\r\n    };\r\n  }\r\n\r\n  const nextState = {};\r\n  let hasChanged = false;\r\n\r\n  for (let contact of Object.keys(state)) {\r\n    nextState[contact] = chatViewReducer(state[contact], action, contact);\r\n    hasChanged = hasChanged || nextState[contact]  !== state[contact];\r\n  }\r\n\r\n  return hasChanged ? nextState : state;\r\n};\r\n```\r\n\r\nOnce again, this change allows `chatViewReducers` to be built from smaller parts each accepting `contact` as third argument, allowing them to filter incoming actions by `contact` to see if they correspond to that particular chat view.\r\n\r\nRight now I have a custom `combineReducers` version that is almost the same as original.\r\n\r\nAlso, the above pattern can be generalised as reducer factory:\r\n\r\n```js\r\n/**\r\n * @param childReducer reducer for state branches\r\n * @param shouldAddKey function that accepts action and returns undefined or \r\n *   string key to be added to state\r\n * @param shouldRemoveKey function that accepts action and returns undefined or\r\n *   string key to be removed from state\r\n */\r\nfunction createMappingReducer(childReducer, shouldAddKey, shouldRemoveKey) {\r\n  ...\r\n}\r\n\r\nconst chatsReducer = createMappingReducer(chatViewReducer, (action) => {\r\n  if (action.type === 'CHAT_OPENED')\r\n    return action.contact;\r\n});\r\n```","created_at":"2016-08-21T07:46:06Z","updated_at":"2016-08-21T07:46:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/reactjs/redux/pulls/1904/commits","review_comments_url":"https://api.github.com/repos/reactjs/redux/pulls/1904/comments","review_comment_url":"https://api.github.com/repos/reactjs/redux/pulls/comments{/number}","comments_url":"https://api.github.com/repos/reactjs/redux/issues/1904/comments","statuses_url":"https://api.github.com/repos/reactjs/redux/statuses/0a43a7bdf4f9371a36d0f08cf5fffb1d817f4790","head":{"label":"aikoven:combine-reducers-extra-args","ref":"combine-reducers-extra-args","sha":"0a43a7bdf4f9371a36d0f08cf5fffb1d817f4790","user":{"login":"aikoven","id":857709,"avatar_url":"https://avatars.githubusercontent.com/u/857709?v=3","gravatar_id":"","url":"https://api.github.com/users/aikoven","html_url":"https://github.com/aikoven","followers_url":"https://api.github.com/users/aikoven/followers","following_url":"https://api.github.com/users/aikoven/following{/other_user}","gists_url":"https://api.github.com/users/aikoven/gists{/gist_id}","starred_url":"https://api.github.com/users/aikoven/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aikoven/subscriptions","organizations_url":"https://api.github.com/users/aikoven/orgs","repos_url":"https://api.github.com/users/aikoven/repos","events_url":"https://api.github.com/users/aikoven/events{/privacy}","received_events_url":"https://api.github.com/users/aikoven/received_events","type":"User","site_admin":false},"repo":{"id":51847740,"name":"redux","full_name":"aikoven/redux","owner":{"login":"aikoven","id":857709,"avatar_url":"https://avatars.githubusercontent.com/u/857709?v=3","gravatar_id":"","url":"https://api.github.com/users/aikoven","html_url":"https://github.com/aikoven","followers_url":"https://api.github.com/users/aikoven/followers","following_url":"https://api.github.com/users/aikoven/following{/other_user}","gists_url":"https://api.github.com/users/aikoven/gists{/gist_id}","starred_url":"https://api.github.com/users/aikoven/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aikoven/subscriptions","organizations_url":"https://api.github.com/users/aikoven/orgs","repos_url":"https://api.github.com/users/aikoven/repos","events_url":"https://api.github.com/users/aikoven/events{/privacy}","received_events_url":"https://api.github.com/users/aikoven/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/aikoven/redux","description":"Predictable state container for JavaScript apps","fork":true,"url":"https://api.github.com/repos/aikoven/redux","forks_url":"https://api.github.com/repos/aikoven/redux/forks","keys_url":"https://api.github.com/repos/aikoven/redux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aikoven/redux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aikoven/redux/teams","hooks_url":"https://api.github.com/repos/aikoven/redux/hooks","issue_events_url":"https://api.github.com/repos/aikoven/redux/issues/events{/number}","events_url":"https://api.github.com/repos/aikoven/redux/events","assignees_url":"https://api.github.com/repos/aikoven/redux/assignees{/user}","branches_url":"https://api.github.com/repos/aikoven/redux/branches{/branch}","tags_url":"https://api.github.com/repos/aikoven/redux/tags","blobs_url":"https://api.github.com/repos/aikoven/redux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aikoven/redux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aikoven/redux/git/refs{/sha}","trees_url":"https://api.github.com/repos/aikoven/redux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aikoven/redux/statuses/{sha}","languages_url":"https://api.github.com/repos/aikoven/redux/languages","stargazers_url":"https://api.github.com/repos/aikoven/redux/stargazers","contributors_url":"https://api.github.com/repos/aikoven/redux/contributors","subscribers_url":"https://api.github.com/repos/aikoven/redux/subscribers","subscription_url":"https://api.github.com/repos/aikoven/redux/subscription","commits_url":"https://api.github.com/repos/aikoven/redux/commits{/sha}","git_commits_url":"https://api.github.com/repos/aikoven/redux/git/commits{/sha}","comments_url":"https://api.github.com/repos/aikoven/redux/comments{/number}","issue_comment_url":"https://api.github.com/repos/aikoven/redux/issues/comments{/number}","contents_url":"https://api.github.com/repos/aikoven/redux/contents/{+path}","compare_url":"https://api.github.com/repos/aikoven/redux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aikoven/redux/merges","archive_url":"https://api.github.com/repos/aikoven/redux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aikoven/redux/downloads","issues_url":"https://api.github.com/repos/aikoven/redux/issues{/number}","pulls_url":"https://api.github.com/repos/aikoven/redux/pulls{/number}","milestones_url":"https://api.github.com/repos/aikoven/redux/milestones{/number}","notifications_url":"https://api.github.com/repos/aikoven/redux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aikoven/redux/labels{/name}","releases_url":"https://api.github.com/repos/aikoven/redux/releases{/id}","deployments_url":"https://api.github.com/repos/aikoven/redux/deployments","created_at":"2016-02-16T15:47:06Z","updated_at":"2016-02-16T15:47:07Z","pushed_at":"2016-08-21T06:13:04Z","git_url":"git://github.com/aikoven/redux.git","ssh_url":"git@github.com:aikoven/redux.git","clone_url":"https://github.com/aikoven/redux.git","svn_url":"https://github.com/aikoven/redux","homepage":"http://redux.js.org","size":3507,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"reactjs:master","ref":"master","sha":"14834591ea8525e373b5ab660af406d8cc847358","user":{"login":"reactjs","id":6412038,"avatar_url":"https://avatars.githubusercontent.com/u/6412038?v=3","gravatar_id":"","url":"https://api.github.com/users/reactjs","html_url":"https://github.com/reactjs","followers_url":"https://api.github.com/users/reactjs/followers","following_url":"https://api.github.com/users/reactjs/following{/other_user}","gists_url":"https://api.github.com/users/reactjs/gists{/gist_id}","starred_url":"https://api.github.com/users/reactjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reactjs/subscriptions","organizations_url":"https://api.github.com/users/reactjs/orgs","repos_url":"https://api.github.com/users/reactjs/repos","events_url":"https://api.github.com/users/reactjs/events{/privacy}","received_events_url":"https://api.github.com/users/reactjs/received_events","type":"Organization","site_admin":false},"repo":{"id":36535156,"name":"redux","full_name":"reactjs/redux","owner":{"login":"reactjs","id":6412038,"avatar_url":"https://avatars.githubusercontent.com/u/6412038?v=3","gravatar_id":"","url":"https://api.github.com/users/reactjs","html_url":"https://github.com/reactjs","followers_url":"https://api.github.com/users/reactjs/followers","following_url":"https://api.github.com/users/reactjs/following{/other_user}","gists_url":"https://api.github.com/users/reactjs/gists{/gist_id}","starred_url":"https://api.github.com/users/reactjs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/reactjs/subscriptions","organizations_url":"https://api.github.com/users/reactjs/orgs","repos_url":"https://api.github.com/users/reactjs/repos","events_url":"https://api.github.com/users/reactjs/events{/privacy}","received_events_url":"https://api.github.com/users/reactjs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/reactjs/redux","description":"Predictable state container for JavaScript apps","fork":false,"url":"https://api.github.com/repos/reactjs/redux","forks_url":"https://api.github.com/repos/reactjs/redux/forks","keys_url":"https://api.github.com/repos/reactjs/redux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/reactjs/redux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/reactjs/redux/teams","hooks_url":"https://api.github.com/repos/reactjs/redux/hooks","issue_events_url":"https://api.github.com/repos/reactjs/redux/issues/events{/number}","events_url":"https://api.github.com/repos/reactjs/redux/events","assignees_url":"https://api.github.com/repos/reactjs/redux/assignees{/user}","branches_url":"https://api.github.com/repos/reactjs/redux/branches{/branch}","tags_url":"https://api.github.com/repos/reactjs/redux/tags","blobs_url":"https://api.github.com/repos/reactjs/redux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/reactjs/redux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/reactjs/redux/git/refs{/sha}","trees_url":"https://api.github.com/repos/reactjs/redux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/reactjs/redux/statuses/{sha}","languages_url":"https://api.github.com/repos/reactjs/redux/languages","stargazers_url":"https://api.github.com/repos/reactjs/redux/stargazers","contributors_url":"https://api.github.com/repos/reactjs/redux/contributors","subscribers_url":"https://api.github.com/repos/reactjs/redux/subscribers","subscription_url":"https://api.github.com/repos/reactjs/redux/subscription","commits_url":"https://api.github.com/repos/reactjs/redux/commits{/sha}","git_commits_url":"https://api.github.com/repos/reactjs/redux/git/commits{/sha}","comments_url":"https://api.github.com/repos/reactjs/redux/comments{/number}","issue_comment_url":"https://api.github.com/repos/reactjs/redux/issues/comments{/number}","contents_url":"https://api.github.com/repos/reactjs/redux/contents/{+path}","compare_url":"https://api.github.com/repos/reactjs/redux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/reactjs/redux/merges","archive_url":"https://api.github.com/repos/reactjs/redux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/reactjs/redux/downloads","issues_url":"https://api.github.com/repos/reactjs/redux/issues{/number}","pulls_url":"https://api.github.com/repos/reactjs/redux/pulls{/number}","milestones_url":"https://api.github.com/repos/reactjs/redux/milestones{/number}","notifications_url":"https://api.github.com/repos/reactjs/redux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/reactjs/redux/labels{/name}","releases_url":"https://api.github.com/repos/reactjs/redux/releases{/id}","deployments_url":"https://api.github.com/repos/reactjs/redux/deployments","created_at":"2015-05-29T23:53:15Z","updated_at":"2016-08-21T05:44:53Z","pushed_at":"2016-08-19T18:52:26Z","git_url":"git://github.com/reactjs/redux.git","ssh_url":"git@github.com:reactjs/redux.git","clone_url":"https://github.com/reactjs/redux.git","svn_url":"https://github.com/reactjs/redux","homepage":"http://redux.js.org","size":13021,"stargazers_count":22092,"watchers_count":22092,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":3341,"mirror_url":null,"open_issues_count":70,"forks":3341,"open_issues":70,"watchers":22092,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/reactjs/redux/pulls/1904"},"html":{"href":"https://github.com/reactjs/redux/pull/1904"},"issue":{"href":"https://api.github.com/repos/reactjs/redux/issues/1904"},"comments":{"href":"https://api.github.com/repos/reactjs/redux/issues/1904/comments"},"review_comments":{"href":"https://api.github.com/repos/reactjs/redux/pulls/1904/comments"},"review_comment":{"href":"https://api.github.com/repos/reactjs/redux/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/reactjs/redux/pulls/1904/commits"},"statuses":{"href":"https://api.github.com/repos/reactjs/redux/statuses/0a43a7bdf4f9371a36d0f08cf5fffb1d817f4790"}},"merged":false,"mergeable":null,"mergeable_state":"unknown","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":2,"deletions":2,"changed_files":1}}
{"actor":{"display_login":"aikoven"}}