{"id":13793458,"url":"https://github.com/salsita/redux-side-effects","last_synced_at":"2025-05-12T20:31:02.978Z","repository":{"id":57351562,"uuid":"46794818","full_name":"salsita/redux-side-effects","owner":"salsita","description":"Redux toolset for keeping all the side effects inside your reducers while maintaining their purity.","archived":false,"fork":false,"pushed_at":"2016-08-22T15:09:37.000Z","size":59,"stargazers_count":180,"open_issues_count":6,"forks_count":8,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-13T05:36:44.096Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salsita.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-24T13:54:18.000Z","updated_at":"2024-09-29T10:56:49.000Z","dependencies_parsed_at":"2022-08-31T06:11:10.179Z","dependency_job_id":null,"html_url":"https://github.com/salsita/redux-side-effects","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fredux-side-effects","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fredux-side-effects/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fredux-side-effects/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fredux-side-effects/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salsita","download_url":"https://codeload.github.com/salsita/redux-side-effects/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253816692,"owners_count":21968867,"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":[],"created_at":"2024-08-03T23:00:21.668Z","updated_at":"2025-05-12T20:31:02.576Z","avatar_url":"https://github.com/salsita.png","language":"JavaScript","funding_links":[],"categories":["Utilities","目录"],"sub_categories":["Side Effects","\u003ca id=\"state\"\u003e状态管理\u003c/a\u003e"],"readme":"redux-side-effects\n=============\n\n[![NPM version][npm-image]][npm-url]\n[![Dependencies][dependencies]][npm-url]\n[![Build status][travis-image]][travis-url]\n[![Downloads][downloads-image]][downloads-url]\n\n\n\u003e What if your reducers were generators? You could yield side effects and return application state.\n\nBelieve it or not, but side effects are still tied with your application's domain. Ideally, you would be able to keep them in reducers. But wait! Everybody is saying that reducers must be pure! So yeah, just keep the reducer pure and reduce side effects as well.\n\n## Why?\n\nSome people (I am one of them) believe that [Elm](https://github.com/evancz/elm-architecture-tutorial/#example-5-random-gif-viewer) has found the proper way how to handle side effects. Yes, we have a solution for async code in [redux](https://github.com/rackt/redux) and it's [`redux-thunk`](https://github.com/gaearon/redux-thunk) but the solution has two major drawbacks:\n\n1) Application logic is not in one place, which leads to the state where business domain may be encapsulated by service domain.\n\n2) Unit testing of some use cases which heavy relies on side effect is nearly impossible. Yes, you can always test those things in isolation but then you will lose the context. It's breaking the logic apart, which is making it basically impossible to test as single unit.\n\nTherefore ideal solution is to keep the domain logic where it belongs (reducers) and abstract away execution of side effects. Which means that your reducers will still be pure (Yes! Also hot-reloadable and easily testable). There are basically two options, either we can abuse reducer's reduction (which is basically a form of I/O Monad) or we can simply put a bit more syntactic sugar on it.\n\nBecause ES6 [generators](https://developer.mozilla.org/cs/docs/Web/JavaScript/Reference/Statements/function*) is an excellent way how to perform lazy evaluation, it's also a perfect tool for the syntax sugar to simplify working with side effects.\n\nJust imagine, you can `yield` a side effect and framework runtime is responsible for executing it after `reducer` reduces new application state. This ensures that Reducer remains pure.\n\n```javascript\nimport { sideEffect } from 'redux-side-effects';\n\nconst loggingEffect = (dispatch, message) =\u003e console.log(message);\n\nfunction* reducer(appState = 1, action) {\n  yield sideEffect(loggingEffect, 'This is side effect');\n\n  return appState + 1;\n}\n```\n\nThe function is technically pure because it does not execute any side effects and given the same arguments the result is still the same.\n\n## Usage\n\nAPI of this library is fairly simple, the only possible functions are `createEffectCapableStore` and `sideEffect`. `createEffectCapableStore` is a store enhancer which enables us to use Reducers in form of Generators. `sideEffect` returns declarative Side effect and allows us easy testing. In order to use it in your application you need to import it, keep in mind that it's [named import](http://www.2ality.com/2014/09/es6-modules-final.html#named_exports_%28several_per_module%29) therefore following construct is correct:\n\n`import { createEffectCapableStore } from 'redux-side-effects'`\n\nThe function is responsible for creating Redux store factory. It takes just one argument which is original Redux [`createStore`](http://redux.js.org/docs/api/createStore.html) function. Of course you can provide your own enhanced implementation of `createStore` factory.\n\nTo create the store it's possible to do the following:\n\n```javascript\nimport { createStore } from 'redux';\nimport { createEffectCapableStore } from 'redux-side-effects';\n\nconst reducer = appState =\u003e appState;\n\nconst storeFactory = createEffectCapableStore(createStore);\nconst store = storeFactory(reducer);\n\n```\n\nBasically something like this should be fully functional:\n\n```javascript\nimport React from 'react';\nimport { render } from 'react-dom';\nimport { createStore } from 'redux';\nimport { createEffectCapableStore, sideEffect } from 'redux-side-effects';\n\nimport * as API from './API';\n\nconst storeFactory = createEffectCapableStore(createStore);\n\nconst addTodoEffect = (dispatch, todo) =\u003e API.addTodo(todo).then(() =\u003e dispatch({type: 'TODO_ADDED'}));\n\nconst store = storeFactory(function*(appState = {todos: [], loading: false}, action) {\n  if (action.type === 'ADD_TODO') {\n    yield sideEffect(addTodoEffect, action.payload);\n\n    return {...appState, todos: [...appState.todos, action.payload], loading: true};\n  } else if (action.type === 'TODO_ADDED') {\n    return {...appState, loading: false};\n  } else {\n    return appState;\n  }\n});\n\nrender(\u003cApplication store={store} /\u003e, document.getElementById('app-container'));\n\n```\n\n## Declarative Side Effects\n\nThe `sideEffect` function is just a very simple declarative way how to express any Side Effect. Basically you can only `yield` result of the function and the function must be called with at least one argument which is Side Effect execution implementation function, all the additional arguments will be passed as arguments to your Side Effect execution implementation function.\n\n```javascript\nconst effectImplementation = (dispatch, arg1, arg2, arg3) =\u003e {\n  // Your Side Effect implementation\n};\n\n\nyield sideEffect(effectImplementation, 'arg1', 'arg2', 'arg3'....);\n```\n\nBe aware that first argument provided to Side Effect implementation function is always `dispatch` so that you can `dispatch` new actions within Side Effect.\n\n## Unit testing\n\nUnit Testing with `redux-side-effects` is a breeze. You just need to assert iterable which is result of Reducer.\n\n```javascript\nfunction* reducer(appState) {\n  if (appState === 42) {\n    yield sideEffect(fooEffect, 'arg1');\n\n    return 1;\n  } else {\n    return 0;\n  }\n}\n\n// Now we can effectively assert whether app state is correctly mutated and side effect is yielded.\nit('should yield fooEffect with arg1 when condition is met', () =\u003e {\n  const iterable = reducer(42);\n\n  assert.deepEqual(iterable.next(), {\n    done: false,\n    value: sideEffect(fooEffect, 'arg1')\n  });\n  assert.equal(iterable.next(), {\n    done: true,\n    value: 1\n  });\n})\n\n```\n\n\n## Example\n\nThere's very simple fully working example including unit tests inside `example` folder.\n\nYou can check it out by:\n```\ncd example\nnpm install\nnpm start\nopen http://localhost:3000\n```\n\nOr you can run tests by\n```\ncd example\nnpm install\nnpm test\n```\n\n## Contribution\n\nIn case you are interested in contribution, feel free to send a PR. Keep in mind that any created issue is much appreciated. For local development:\n\n```\n  npm install\n  npm run test:watch\n```\n\nYou can also `npm link` the repo to your local Redux application so that it's possible to test the expected behaviour in real Redux application.\n\nPlease for any PR, don't forget to write unit test.\n\n## Need Help?\n\nYou can reach me on [reactiflux](http://www.reactiflux.com) - username tomkis1, or DM me on [twitter](https://twitter.com/tomkisw).\n\n## FAQ\n\n\u003e Does redux-side-effects work with working Redux application?\n\nYes! I set this as the major condition when started thinking about this library. Therefore the API is completely backwards compatible with any\nRedux application.\n\n\u003e My middlewares are not working anymore, what has just happened?\n\nIf you are using middlewares you have to use `createEffectCapableStore` for middleware enhanced store factory, not vice versa. Meaning:\n\n```javascript\n    const createStoreWithMiddleware = applyMiddleware(test)(createStore);\n    const storeFactory = createEffectCapableStore(createStoreWithMiddleware);\n    const store = storeFactory(reducer);\n```\n\nis correct.\n\n\u003e Can I compose reducers?\n\nYes! `yield*` can help you with the composition. The concept is explained in this [gist](https://gist.github.com/tomkis1/236f6ba182b48fde4dc9)\n\n\u003e I keep getting warning \"createEffectCapableStore enhancer from redux-side-effects package is used yet the provided root reducer is not a generator function\", what does that mean?\n\nKeep in mind that your root reducer needs to be generator function therefore this will throw the warning:\n\n```javascript\nconst storeFactory = createEffectCapableStore(createStore);\nconst store = storeFactory(function(appState) { return appState; });\n```\n\nbut this will work:\n\n```javascript\nconst storeFactory = createEffectCapableStore(createStore);\nconst store = storeFactory(function* (appState) { return appState; });\n```\n\n\u003e Can I use ()* =\u003e {} instead of function*()?\n\nUnfortunately no. Only `function*` is valid ES6 syntax.\n\n\u003e I am using combineReducers, how does this work with redux-side-effects?\n\nIf you are using standard Redux [`combineReducer`](http://rackt.org/redux/docs/api/combineReducers.html) in your application, please use the imported version from this package, original implementation does not work with generators. However, keep in mind that this method is [opinionated](http://rackt.org/redux/docs/api/combineReducers.html#notes) and therefore you should probably provide your own implementation.\n\nUsage is simple:\n\n`import { combineReducers } from 'redux-side-effects'`\n\n\n[npm-image]: https://img.shields.io/npm/v/redux-side-effects.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/redux-side-effects\n[travis-image]: https://img.shields.io/travis/salsita/redux-side-effects.svg?style=flat-square\n[travis-url]: https://travis-ci.org/salsita/redux-side-effects\n[downloads-image]: http://img.shields.io/npm/dm/redux-side-effects.svg?style=flat-square\n[downloads-url]: https://npmjs.org/package/redux-side-effects\n[dependencies]: https://david-dm.org/salsita/redux-side-effects.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalsita%2Fredux-side-effects","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalsita%2Fredux-side-effects","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalsita%2Fredux-side-effects/lists"}