{"_id":"@darcusfenix/redux-saga-routines","_rev":"1-b337dd85ad87724630e1c91434e3d104","name":"@darcusfenix/redux-saga-routines","dist-tags":{"latest":"2.2.6"},"versions":{"2.2.6":{"name":"@darcusfenix/redux-saga-routines","version":"2.2.6","description":"Routines for redux-saga also useful with redux-form","keywords":["redux","saga","redux-saga","routines","redux-saga-routines","redux-form"],"main":"dist/index.js","types":"src/index.d.ts","module":"es/index.js","jsnext:main":"es/index.js","devDependencies":{"@types/redux-actions":"^2.2.2","@types/redux-form":"^7.0.6","babel-cli":"^6.24.1","babel-eslint":"^7.2.3","babel-plugin-transform-runtime":"^6.23.0","babel-preset-es2015":"^6.24.1","babel-preset-stage-2":"^6.24.1","babel-register":"^6.24.1","babel-runtime":"*","chai":"^3.4.1","cross-env":"^4.0.0","eslint":"^2.4.0","eslint-plugin-babel":"^3.0.0","mocha":"^2.3.4","redux-saga":">=0.15.0","rimraf":"^2.5.4","sinon":"^2.1.0"},"peerDependencies":{"redux-saga":">=0.15.0"},"repository":{"type":"git","url":"git+ssh://git@github.com/afitiskin/redux-saga-routines.git"},"author":{"name":"Alex Fitiskin","email":"alexfitiskin@gmail.com"},"contributors":[{"name":"Alex Fitiskin","email":"alexfitiskin@gmail.com"},{"name":"Mark Haussmann","email":"mark.haussmann@gmail.com"},{"name":"Karol Janyst","email":"lapkom@gmail.com"}],"maintainers":[{"name":"darcusfenix","email":"juancvfenix@gmail.com"}],"bugs":{"url":"https://github.com/afitiskin/redux-saga-routines/issues"},"scripts":{"lint":"eslint ./{src,test}/**/*.{js,jsx}","test":"mocha --compilers js:babel-register test","test:watch":"npm run test -- --watch","prebuild":"npm run test && npm run lint && rimraf dist && rimraf es","build:cjs":"cross-env BABEL_ENV=development babel src/ --out-dir dist","build:es":"cross-env BABEL_ENV=es babel src/ --out-dir es","build":"npm run build:cjs && npm run build:es","prepublishOnly":"npm run build"},"license":"MIT","dependencies":{"redux-actions":"^2.6.5"},"gitHead":"f143b843d205c706ddeaf0e26b834210910434e0","homepage":"https://github.com/afitiskin/redux-saga-routines#readme","_id":"@darcusfenix/redux-saga-routines@2.2.6","_nodeVersion":"8.10.0","_npmVersion":"6.14.4","dist":{"integrity":"sha512-SLFaCENPKyLfzI6bj2PKH3zdrzgVzdJM3x0vbSfYOhSqGam/2BKJtCSS+F7zXsq2CUwPDfmpBJj1uZYddsqFeA==","shasum":"d1a8550ae7963c19ee3c76ea3c026ec25790a57f","tarball":"https://registry.npmjs.org/@darcusfenix/redux-saga-routines/-/redux-saga-routines-2.2.6.tgz","fileCount":28,"unpackedSize":41065,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJejMq8CRA9TVsSAnZWagAAqMEP/jO0xN2eAC1p8SknSXey\nOt7/Gsp0N5J658zu1wI5+y2pCmGkAYTjgkAvYyNIjYr7/f0XICv5vIfbg+tA\n3uXqDeorqrQSiT+H7wPRvvZI+3nahiyyzU8mRezcfBVgxIvg1bQEHdp20urD\n+0cHHy3OGlWHPvE1S1DdUe0c1Me8vJszdbbPG4B5ZMQmmiRPe0HLO+hfnFGQ\nKBut7RdFzq5SGIpBCCsQq25vrAwRww9yB0HDURsklUb41IpK4KJo1g9Ki5TF\nbyqC7g2ilokDZ0GrM1bfeyfo9lX9ATWIvCjgZm3IMh3hEpGCX5BEaMNcQmaD\nO2eifzH2bqJkRDNc6ZyL17k11ZPTrpMMsQLuq7KTJ/YDteWMbG8u2bEO8F0N\nQ5vQPG/D60Njj7lRdYruSCEX+MOR9dNiKCRvAdyLpVXKjKlyhFqbftxVv9tR\nRfalzH+tJwaJAdI+k8QGJuf+hyfKOx0RYpkyrUwubni4OG8GCPFFqJN0aHeG\nc7ZM1uyBWYKwu+CZMzy0/HUW+lWoeGtDCHKYz+LAynR8GhsujAw7cVeDl3fW\nn/w5zF+nP7c/7o3I92Lmg24SyURYT/oSb4XaXaUhyk10BFC9soqwLBVG/inB\n14hBelr0/o3bWnXlT5mD2tOs2p/eY31pIUq7hANw3/pR4njPF1657bHrnKM/\nTTya\r\n=trhf\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQC45Ecf5ExE/gxv0Xml4AYlL6hYNEwAl4RIFGR9unRH8gIhANJzNSJqVgcWpIjyVrEkqqyRWME1IydreZnvYUgAtZ6i"}]},"_npmUser":{"name":"darcusfenix","email":"juancvfenix@gmail.com"},"directories":{},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/redux-saga-routines_2.2.6_1586285243874_0.7518492416464302"},"_hasShrinkwrap":false}},"time":{"created":"2020-04-07T18:47:21.539Z","2.2.6":"2020-04-07T18:47:23.988Z","modified":"2022-04-05T02:44:29.367Z"},"maintainers":[{"name":"darcusfenix","email":"juancvfenix@gmail.com"}],"description":"Routines for redux-saga also useful with redux-form","homepage":"https://github.com/afitiskin/redux-saga-routines#readme","keywords":["redux","saga","redux-saga","routines","redux-saga-routines","redux-form"],"repository":{"type":"git","url":"git+ssh://git@github.com/afitiskin/redux-saga-routines.git"},"contributors":[{"name":"Alex Fitiskin","email":"alexfitiskin@gmail.com"},{"name":"Mark Haussmann","email":"mark.haussmann@gmail.com"},{"name":"Karol Janyst","email":"lapkom@gmail.com"}],"author":{"name":"Alex Fitiskin","email":"alexfitiskin@gmail.com"},"bugs":{"url":"https://github.com/afitiskin/redux-saga-routines/issues"},"license":"MIT","readme":"# redux-saga-routines\nA smart action creator for [Redux](https://github.com/reactjs/redux). Useful for any kind of async actions like fetching data.\nAlso fully compatible with [Redux Saga](https://github.com/yelouafi/redux-saga) and [Redux Form](https://github.com/erikras/redux-form).\n\n## Version 2\nModule was totally reworked since version 2.0.0. If you still using version 1.* see [version 1 docs](https://github.com/afitiskin/redux-saga-routines/tree/v1)\n\n## Why do I need this?\n\nReduce boilerplate from your source code when making requests to API or validating forms build on top of [Redux Form](https://github.com/erikras/redux-form).\n\n## Installation\n\n```javascript\nyarn add redux-saga-routines\n```\n\n## What is routine?\nRoutine is a smart action creator that encapsulates 5 action types and 5 action creators to make standard actions lifecycle easy-to-use:\nTRIGGER -> REQUEST -> SUCCESS / FAILURE -> FULFILL\n\nSo, with `redux-saga-routines` you don't need to create all these action type constants and action creators manually, just use `createRoutine`:\n\n```javascript\nimport { createRoutine } from 'redux-saga-routines';\n\n// creating routine\nconst routine = createRoutine('ACTION_TYPE_PREFIX');\n```\n\n`'ACTION_TYPE_PREFIX'` passed to `createRoutine` is a name of routine (and a prefix for all it's action types).\n\nYou can access all action types using `TRIGGER`, `REQUEST`, `SUCCESS`, `FAILURE`, `FULFILL` attributes of `routine` object:\n```javascript\nroutine.TRIGGER === 'ACTION_TYPE_PREFIX/TRIGGER';\nroutine.REQUEST === 'ACTION_TYPE_PREFIX/REQUEST';\nroutine.SUCCESS === 'ACTION_TYPE_PREFIX/SUCCESS';\nroutine.FAILURE === 'ACTION_TYPE_PREFIX/FAILURE';\nroutine.FULFILL === 'ACTION_TYPE_PREFIX/FULFILL';\n```\n\nYou also have 5 action creators: `trigger`, `request`, `success`, `failure`, `fulfill`:\n```javascript\nroutine.trigger(payload) === { type: 'ACTION_TYPE_PREFIX/TRIGGER', payload };\nroutine.request(payload) === { type: 'ACTION_TYPE_PREFIX/REQUEST', payload };\nroutine.success(payload) === { type: 'ACTION_TYPE_PREFIX/SUCCESS', payload };\nroutine.failure(payload) === { type: 'ACTION_TYPE_PREFIX/FAILURE', payload };\nroutine.fulfill(payload) === { type: 'ACTION_TYPE_PREFIX/FULFILL', payload };\n```\n\nRoutine by itself is a trigger action creator function:\n```javascript\nexpect(routine(payload)).to.deep.equal(routine.trigger(payload));\n```\n\n`redux-saga-routines` based on [redux-actions](https://github.com/reduxactions/redux-actions), so `createRoutine` actually accepts 3 parameters: `(actionTypePrefix, payloadCreator, metaCreator) => function`.\nEvery routine action creator is a  `redux-actions` FSA, so you can use them with `handleAction(s)` or `combineActions` from `redux-actions`\n\n## Usage\n### Example: fetching data from server\n\nLet's start with creating routine for fetching some data from server:\n```javascript\n// routines.js\n\nimport { createRoutine } from 'redux-saga-routines';\nexport const fetchData = createRoutine('FETCH_DATA');\n```\n\nThen, let's create some component, that triggers data fetching:\n```javascript\n// FetchButton.js\n\nimport { connect } from 'react-redux';\nimport { fetchData } from './routines'; // import our routine\n\nclass FetchButton extends React.Component {\n  static mapStateToProps = (state) => {\n    return {...}; // map some state to component props\n  }\n  static mapDispatchToProps = {\n    fetchData,\n  };\n\n  onClick() {\n    this.props.fetchData(); // dispatching routine trigger action\n  }\n\n  render() {\n    return (\n      <button onClick={() => this.onClick()}>\n        Fetch data from server\n      </button>\n    );\n  }\n}\n\nexport default connect(FetchButton.mapStateToProps, FetchButton.mapDispatchToProps)(FetchButton);\n```\n\nNow, let's take a look at reducer example:\n```javascript\n// reducer.js\n\nimport { fetchData } from './routines';\n\nconst initialState = {\n  data: null,\n  loading: false,\n  error: null,\n};\n\nexport default function exampleReducer(state = initialState, action) {\n  switch (action.type) {\n    case fetchData.TRIGGER:\n      return {\n        ...state,\n        loading: true,\n      };\n    case fetchData.SUCCESS:\n      return {\n        ...state,\n        data: action.payload,\n      };\n    case fetchData.FAILURE:\n      return {\n        ...state,\n        error: action.payload,\n      };\n    case fetchData.FULFILL:\n      return {\n        ...state,\n        loading: false,\n      };\n    default:\n      return state;\n  }\n}\n```\n\nAnd, saga (but you can use any other middleware, like `redux-thunk`):\n```javascript\n// saga.js\n\nimport { fetchData } from './routines';\n\nfunction* requestWatcherSaga() {\n  // run fetchDataFromServer on every trigger action\n  yield takeEvery(fetchData.TRIGGER, fetchDataFromServer)\n}\n\nfunction* fetchDataFromServer() {\n  try {\n    // trigger request action\n    yield put(fetchData.request());\n    // perform request to '/some_url' to fetch some data\n    const response = yield call(apiClient.request, '/some_url');\n    // if request successfully finished\n    yield put(fetchData.success(response.data));\n  } catch (error) {\n    // if request failed\n    yield put(fetchData.failure(error.message));\n  } finally {\n    // trigger fulfill action\n    yield put(fetchData.fulfill());\n  }\n}\n```\n\n### Filtering actions\nIt is a common case to ignore some triggered actions and not to perform API request every time.\nFor example, let's make a saga, that perform API request only on odd button clicks (1st, 3rd, 5th, ...):\n```javascript\n// saga.js\n\nimport { fetchData } from './routines';\n\nfunction* requestWatcherSaga() {\n  // run handleTriggerAction on every trigger action\n  yield takeEvery(fetchData.TRIGGER, handleTriggerAction)\n}\n\nlet counter = 0;\nfunction* handleTriggerAction() {\n  if (counter++ % 2 === 0) {\n    // perform API request only on odd calls\n    yield call(fetchDataFromServer);\n  }\n\n  // trigger fulfill action to finish routine lifecycle on every click\n  yield put(fetchData.fulfill());\n}\n\nfunction* fetchDataFromServer() {\n  try {\n    // trigger request action\n    yield put(fetchData.request());\n    // perform request to '/some_url' to fetch some data\n    const response = yield call(apiClient.request, '/some_url');\n    // if request successfully finished\n    yield put(fetchData.success(response.data));\n  } catch (error) {\n    // if request failed\n    yield put(fetchData.failure(error.message));\n  }\n}\n```\n\n### Wrap routine into promise\nSometimes it is useful to use promises (especially with 3rd-party components). With `redux-saga-routines` you are able to wrap your routine into promise and handle it in your saga!\nTo achive this just add `routinePromiseWatcherSaga` in your `sagaMiddleware.run()`, for example like this:\n```javascript\nimport { routinePromiseWatcherSaga } from 'redux-saga-routines';\n\nconst sagas = [\n  yourFirstSaga,\n  yourOtherSaga,\n  // ...,\n  routinePromiseWatcherSaga,\n];\nsagas.forEach(sagaMiddleware.run);\n```\n\nNow we are ready. There is special `promisifyRoutine` helper, that wraps your routine in function with signature: `(payload, dispatch) => Promise`.\nSee example below:\nFirst, create routine:\n```javascript\n// routines.js\n\nimport { createRoutine, promisifyRoutine } from 'redux-saga-routines';\nexport const myRoutine = createRoutine('MY_ROUTINE');\nexport const myRoutinePromiseCreator = promisifyRoutine(myRoutine);\n```\n\nThen, use it in your form component:\n```javascript\n// MyComponent.js\nimport { bindPromiseCreators } from 'redux-saga-routines';\nimport { myRoutine, myRoutinePromiseCreator } from './routines';\n\n// since promise creator signature is (values, dispatch) => Promise\n// we have to bind it to dispatch using special helper bindPromiseCreator\n\nclass MyComponent extends React.Component {\n  static mapStateToProps(state) {\n    // return props object from selected from state\n  }\n  \n  static mapDispatchToProps(dispatch) {\n    return {\n      ...bindPromiseCreators({ \n        myRoutinePromiseCreator,\n        // other promise creators can be here... \n      }, dispatch),\n      \n      // here you can use bindActionCreators from redux\n      // to bind simple action creators\n      // ...bindActionCreators({ mySimpleAction1, mySimpleAction2 }, dispatch)\n      \n      // or other helpers to bind other functions to store's dispatch\n      // ...\n      \n      // or just pass dispatch as a prop to component\n      dispatch,\n    };\n  }\n\n\n  handleClick() {\n    const promise = this.props.myRoutinePromiseCreator(somePayload);\n    // so, call of myRoutinePromiseCreator returns promise\n    // you can use this promise as you want\n    \n    promise.then(\n      (successPayload) => console.log('success :)', successPayload),\n      (failurePayload) => console.log('failure :(', failurePayload),\n    );\n    \n    \n    // internally when you call myRoutinePromiseCreator() special action with type ROUTINE_PROMISE_ACTION is dispatched\n    // this special action is handled by routinePromiseWatcherSaga\n    \n    // to resolve promise you need to dispatch myRoutine.success(successPayload) action, successPayload will be passed to resolved promise\n    // if  myRoutine.failure(failurePayload) is dispatched, promise will be rejected with failurePayload.\n    \n    // we just want to wait 5 seconds and then resolve promise with 'voila!' message:\n    setTimeout(\n      () => this.props.dispatch(myRoutine.success('voila!')),\n      5000,\n    );\n    \n    // same example, but with promise rejection:\n    // setTimeout(\n    //   () => this.props.dispatch(myRoutine.failure('something went wrong...')),\n    //   5000,\n    // );\n    \n    // of course you don't have to do it in your component\n    // you can do it in your saga\n    // see below\n  }\n  \n  render() {\n    return (\n      <button onClick={() => this.handleClick()}>\n        {/* your form fields here... */}\n      </form>\n    );\n  }\n}\n\nexport default connect(MyComponent.mapStateToProps, MyComponent.mapDispatchToProps)(MyComponent);\n```\n\nYou are able to resolve/reject given promise in your saga:\n```javascript\n// saga.js\nimport { myRoutine } from './routines';\n\nfunction* myRoutineTriggerWatcher() {\n  // when you call myRoutinePromiseCreator(somePayload) \n  // internally myRoutine.trigger(somePayload) action is dispatched\n  // we take every routine trigger actions and handle them\n  yield takeEvery(myRoutine.TRIGGER, handleTriggerAction)\n}\n\nfunction* handleTriggerAction(action) {\n  const { payload } = action; // here payload is somePayload passed from myRoutinePromiseCreator(somePayload)\n  const isDataCorrect = verifyData(payload);\n\n  if (isDataCorrect) {\n    // send data to server\n    yield call(sendFormDataToServer, payload);\n  } else {\n    // reject given promise\n    yield put(myRoutine.failure('something went wrong'));\n  }\n\n  // trigger fulfill action to end routine lifecycle\n  yield put(myRoutine.fulfill());\n}\n\nfunction* sendFormDataToServer(data) {\n  try {\n    // trigger request action\n    yield put(myRoutine.request());\n    // perform request to '/endpoint'\n    const response = yield call(apiClient.request, '/endpoint', data);\n    // if request successfully finished we resolve promise with response data\n    yield put(myRoutine.success(response.data));\n  } catch (error) {\n    // if request failed we reject promise with error message\n    yield put(myRoutine.failure(error.message);\n  }\n}\n```\n\n\n### `redux-saga`, `redux-form`, `redux-saga-routines` combo\nYou are also allowed to use combo of `redux-saga`, `redux-form` and `redux-saga-routines`!\nSince `redux-form` validation based on promises, you are able to handle `redux-form` validation in your saga.\nTo achive this just add `routinePromiseWatcherSaga` in your `sagaMiddleware.run()`, like in example above.\n\nThere are special `bindRoutineToReduxForm` helper, that wraps your routine in function with `redux-form` compatible signature: `(values, dispatch, props) => Promise` (it works just like `promisifyRoutine` but more specific to be compatible with full `redux-form` functionality)\n\n\nFirst, create routine and it's wrapper for `redux-form`:\n```javascript\n// routines.js\n\nimport { createRoutine, bindRoutineToReduxForm } from 'redux-saga-routines';\nexport const submitFormRoutine = createRoutine('SUBMIT_MY_FORM');\nexport const submitFormHandler = bindRoutineToReduxForm(submitFormRoutine);\n```\n\nThen, use it in your form component:\n```javascript\n// MyForm.js\n\nimport { reduxForm } from 'redux-form';\nimport { submitFormHandler } from './routines';\n\n// you do not need to bind your handler to store, since `redux-form` pass `dispatch` to handler.\n\nclass MyForm extends React.Component {\n  render() {\n    return (\n      <form onSubmit={this.props.handleSubmit(submitFormHandler)}>\n        {/* your form fields here... */}\n      </form>\n    );\n  }\n}\n\nexport default reduxForm()(MyForm);\n```\n\n\nNow you are able to handle form submission in your saga:\n```javascript\n// saga.js\nimport { SubmissionError } from 'redux-form';\nimport { submitFormRoutine } from './routines';\n\nfunction* validateFormWatcherSaga() {\n  // run validation on every trigger action\n  yield takeEvery(submitFormRoutine.TRIGGER, validate)\n}\n\nfunction* validate(action) {\n  // redux-form pass form values and component props to submit handler\n  // so they passed to trigger action as an action payload\n  const { values, props } = action.payload;\n\n  if (!isValid(values, props)) {\n    // client-side validation failed\n    const errors = getFormErrors(values, props);\n    // reject promise given to redux-form, pass errors as SubmissionError object according to redux-form docs\n    yield put(submitFormRoutine.failure(new SubmissionError(errors)));\n  } else {\n    // send form data to server\n    yield call(sendFormDataToServer, values);\n  }\n\n  // trigger fulfill action to end routine lifecycle\n  yield put(submitFormRoutine.fulfill());\n}\n\nfunction* sendFormDataToServer(formData) {\n  try {\n    // trigger request action\n    yield put(submitFormRoutine.request());\n    // perform request to '/submit' to send form data\n    const response = yield call(apiClient.request, '/submit', formData);\n    // if request successfully finished\n    yield put(submitFormRoutine.success(response.data));\n  } catch (error) {\n    // if request failed\n    yield put(submitFormRoutine.failure(new SubmissionError({ _error: error.message })));\n  }\n}\n```\n\n\n## License\n\nMIT\n\n","readmeFilename":"README.md"}