{"action":"created","issue":{"url":"https://api.github.com/repos/gaearon/redux/issues/60","labels_url":"https://api.github.com/repos/gaearon/redux/issues/60/labels{/name}","comments_url":"https://api.github.com/repos/gaearon/redux/issues/60/comments","events_url":"https://api.github.com/repos/gaearon/redux/issues/60/events","html_url":"https://github.com/gaearon/redux/pull/60","id":86397090,"number":60,"title":"Support custom dispatchers","user":{"login":"gaearon","id":810438,"avatar_url":"https://avatars.githubusercontent.com/u/810438?v=3","gravatar_id":"","url":"https://api.github.com/users/gaearon","html_url":"https://github.com/gaearon","followers_url":"https://api.github.com/users/gaearon/followers","following_url":"https://api.github.com/users/gaearon/following{/other_user}","gists_url":"https://api.github.com/users/gaearon/gists{/gist_id}","starred_url":"https://api.github.com/users/gaearon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gaearon/subscriptions","organizations_url":"https://api.github.com/users/gaearon/orgs","repos_url":"https://api.github.com/users/gaearon/repos","events_url":"https://api.github.com/users/gaearon/events{/privacy}","received_events_url":"https://api.github.com/users/gaearon/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":8,"created_at":"2015-06-09T01:21:20Z","updated_at":"2015-06-09T11:17:30Z","closed_at":"2015-06-09T09:33:41Z","pull_request":{"url":"https://api.github.com/repos/gaearon/redux/pulls/60","html_url":"https://github.com/gaearon/redux/pull/60","diff_url":"https://github.com/gaearon/redux/pull/60.diff","patch_url":"https://github.com/gaearon/redux/pull/60.patch"},"body":"Goals of this PR:\r\n\r\n* Add a **single powerful extension point**\r\n* Allow both #55 and #6 to be **implemented in userland**\r\n\r\nThis PR **does not** try to solve middleware API or the middleware composition. Instead, it allows to implement #55 and #6 in the userland by **separating “Redux instance” (the state holder) and the Dispatcher.** By providing a custom Dispatcher, you can experiment with different composable middleware APIs without changing the core.\r\n\r\n#### New definitions\r\n\r\nThe **Dispatcher** is a function responsible for turning actions into state changes. Its signature is `(initialState, setState) => (action) => ()`. You won't use it directly, and will instead hand it off to the Redux instance.\r\n\r\nThe **Redux instance** is ignorant of how actions turn into state. It has no knowledge of dispatching, or stores. It is a facade for Redux, providing you `getState` and `dispatch`, and letting you specify an initial state for hydration.\r\n\r\nThe Redux library provides a **default dispatcher that implements the current behavior** with `createDispatcher`:\r\n\r\n```js\r\nimport { createRedux, createDispatcher, composeStores } from 'redux';\r\nimport * as stores from '../stores/index';\r\n\r\n// Compose all your Stores into a single Store function with `composeStores`:\r\nconst store = composeStores(stores);\r\n\r\n// Create a Dispatcher function for your composite Store:\r\nconst dispatcher = createDispatcher(store);\r\n\r\n// Create a Redux instance using the dispatcher function:\r\nconst redux = createRedux(dispatcher);\r\n```\r\n\r\n#### The built-in Dispatcher\r\n\r\nThe default dispatcher looks like this:\r\n\r\n```js\r\nexport default function createDispatcher(store) {\r\n  return function dispatcher(initialState, setState) {\r\n    let state = store(initialState, {});\r\n    setState(state);\r\n\r\n    function dispatchSync(action) {\r\n      state = store(state, action);\r\n      setState(state);\r\n      return action;\r\n    }\r\n\r\n    function dispatch(action) {\r\n      return typeof action === 'function' ?\r\n        action(dispatch, state) :\r\n        dispatchSync(action);\r\n    }\r\n\r\n    return dispatch;\r\n  };\r\n}\r\n```\r\n\r\nAs you can see, `perform` is no more. **Now `dispatch` behaves exactly like `perform` used to behave** because that's what the Dispatcher does.\r\n\r\n#### Custom Dispatchers\r\n\r\nTo provide a custom dispatcher, just *don't use `createDispatcher`* and pass your own function instead with the same `(initialState, setState) => (action) => ()` signature.\r\n\r\nIt doesn't even *have to* understand the concept of Stores. It may do something completely different—the only important part is that it has to translate the actions into the state updates.\r\n\r\n#### Convenience API\r\n\r\nFinally, this PR brings back a simpler convenience API for most people who don't need to customize the Dispatcher or the Store composition:\r\n\r\n```js\r\nimport { createRedux } from 'redux';\r\nimport * as stores from '../stores/index';\r\n\r\nconst redux = createRedux(stores);\r\n```"},"comment":{"url":"https://api.github.com/repos/gaearon/redux/issues/comments/110321128","html_url":"https://github.com/gaearon/redux/pull/60#issuecomment-110321128","issue_url":"https://api.github.com/repos/gaearon/redux/issues/60","id":110321128,"user":{"login":"gaearon","id":810438,"avatar_url":"https://avatars.githubusercontent.com/u/810438?v=3","gravatar_id":"","url":"https://api.github.com/users/gaearon","html_url":"https://github.com/gaearon","followers_url":"https://api.github.com/users/gaearon/followers","following_url":"https://api.github.com/users/gaearon/following{/other_user}","gists_url":"https://api.github.com/users/gaearon/gists{/gist_id}","starred_url":"https://api.github.com/users/gaearon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gaearon/subscriptions","organizations_url":"https://api.github.com/users/gaearon/orgs","repos_url":"https://api.github.com/users/gaearon/repos","events_url":"https://api.github.com/users/gaearon/events{/privacy}","received_events_url":"https://api.github.com/users/gaearon/received_events","type":"User","site_admin":false},"created_at":"2015-06-09T11:17:30Z","updated_at":"2015-06-09T11:17:30Z","body":"This change has a funny consequence!\r\n\r\nNow I can write `createReplayingDispatcher` and change `createDispatcher` to `createReplayingDispatcher` while the code is running, and the dispatcher will be hot reloaded. Which means I don't even need transactions because I can just write an über dispatcher that switches between those folks with options like this:\r\n\r\n```js\r\nconst dispatcher = createUberDispatcher({ replay: true, log: true }); // can tweak options with hot reloading\r\n```"}}