{"id":13809332,"url":"https://github.com/cyrilluce/saga-duck","last_synced_at":"2025-09-17T08:31:38.980Z","repository":{"id":21928784,"uuid":"94414889","full_name":"cyrilluce/saga-duck","owner":"cyrilluce","description":"extensible and composable duck for redux-saga","archived":false,"fork":false,"pushed_at":"2023-03-04T04:23:33.000Z","size":935,"stargazers_count":98,"open_issues_count":15,"forks_count":15,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-12-18T12:52:06.376Z","etag":null,"topics":["ducks-pattern","redux","redux-saga","saga-duck","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/cyrilluce.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-06-15T07:56:47.000Z","updated_at":"2024-10-23T21:02:31.000Z","dependencies_parsed_at":"2024-01-29T07:28:09.345Z","dependency_job_id":"6f81a18a-b255-4edd-86b9-54a02f87f4cf","html_url":"https://github.com/cyrilluce/saga-duck","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/cyrilluce%2Fsaga-duck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrilluce%2Fsaga-duck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrilluce%2Fsaga-duck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyrilluce%2Fsaga-duck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyrilluce","download_url":"https://codeload.github.com/cyrilluce/saga-duck/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231377964,"owners_count":18367484,"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":["ducks-pattern","redux","redux-saga","saga-duck","typescript"],"created_at":"2024-08-04T01:02:18.965Z","updated_at":"2025-09-17T08:31:38.961Z","avatar_url":"https://github.com/cyrilluce.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003e [!IMPORTANT]\n\u003e This project is no longer maintained\n\n---\n\n# saga-duck\nextensible and composable duck for redux-saga, **typescript 3.x supported**.\n\nSee also\n[ducks-modular-redux](https://github.com/erikras/ducks-modular-redux)\n[extensible-duck](https://github.com/investtools/extensible-duck)\n\n# example\nRun command:\n```sh\nnpm start\n```\n![example of Counters](examples/example.gif)\nSource code are in `examples` directory\n\n# usage\n## install\n```sh\nnpm i saga-duck -S\n```\n\n## Documents\n[Document-中文](https://cyrilluce.gitbook.io/saga-duck/)\n\nfor 2.x please visit\n[Legacy Document-中文](https://cyrilluce.gitbooks.io/saga-duck)\n\n\n## memtion\nDucks should be stateless, so we can use React FSC(functional stateless compoment) and optimize later.\nYou should only access store by duck.selector or duck.selectors.\n\n## single duck\n```js\nimport { Duck } from \"saga-duck\";\nimport { takeEvery, call, put, select } from \"redux-saga/effects\";\nimport { delay } from \"redux-saga\";\n\nclass SingleDuck extends Duck {\n  get quickTypes() {\n    return {\n      ...super.quickTypes,\n      INCREMENT: 1,\n      INCREMENT_ASYNC: 1\n    };\n  }\n  get reducers() {\n    const { types } = this;\n    return {\n      ...super.reducers,\n      count: (state = 0, action) =\u003e {\n        switch (action.type) {\n          case types.INCREMENT:\n            return state + 1;\n          default:\n            return state;\n        }\n      }\n    };\n  }\n  *saga() {\n    yield* super.saga();\n    const { types, selector } = this;\n    yield takeEvery(types.INCREMENT_ASYNC, function*() {\n      yield call(delay, 1000);\n      // select state of this duck\n      const { count } = selector(yield select());\n      yield put({type: types.INCREMENT});\n    });\n  }\n}\n```\n\n## extend duck\n```js\nclass ExtendedDuck extends SingleDuck {\n  get quickTypes(){\n    return {\n      ...super.quickTypes,\n      MORE: 1\n    }\n  }\n  get reducers(){\n    return {\n      ...super.reducers,\n      more: (state, action) =\u003e 1\n    }\n  }\n  get rawSelectors(){\n    return {\n      ...super.rawSelectors,\n      more(state){\n        return state.more\n      }\n    }\n  }\n  get creators(){\n    const { types } = this\n    return {\n      ...super.creators,\n      more(){\n        return {\n          type: types.MORE\n        }\n      }\n    }\n  }\n  *saga(){\n    yield* super.saga()\n    const { types, selector, selectors, creators } = this\n    yield take([types.INCREMENT, types.MORE])\n    const { count, more } = selector(yield select())\n    const _more = selectors.more(yield select())\n    yield put(creators.more())\n  }\n}\n```\n\n## compose ducks\n```js\nimport { ComposableDuck } from \"saga-duck\";\n\nclass ComposedDuck extends ComposableDuck {\n  get quickTypes() {\n    return {\n      ...super.quickTypes,\n      PARENT: 1\n    };\n  }\n  get quickDucks() {\n    return {\n      ...super.quickDucks,\n      duck1: SingleDuck,\n      duck2: ExtendedDuck,\n      duck3: ExtendedDuck\n    };\n  }\n  *saga() {\n    yield* super.saga();\n    const {\n      types,\n      selector,\n      ducks: { duck1, duck2, duck3 }\n    } = this;\n    yield takeEvery(types.PARENT, function*() {\n      yield put({ type: duck1.types.INCREMENT });\n      yield put(duck2.creators.more());\n      yield put(duck3.creators.more());\n    });\n    // { parent, duck1: {count}, duck2: {count, more}, duck3: {count, more} }\n    const state = selector(yield select());\n  }\n}\n```\n\n## Run and connect to React (Legacy style)\n```js\nimport { DuckRuntime } from \"saga-duck\";\nimport Root from \"./Root\";\nimport Duck from \"./RootDuck\";\n\nconst duckRuntime = new DuckRuntime(new Duck({...}));\nconst ConnectedComponent = duckRuntime.connectRoot()(Root);\n\nReactDOM.render(\n  \u003cProvider store={duckRuntime.store}\u003e\n    \u003cConnectedComponent /\u003e\n  \u003c/Provider\u003e,\n  document.getElementById(\"root\")\n);\n```\nRoot.ts (Duck Component)\n```js\nimport * as React from 'react'\nimport Counter from \"./Counter\";\nimport { DuckCmpProps } from 'saga-duck';\nimport Duck from './RootDuck'\n\nexport default function Root({ duck, store, dispatch }: DuckCmpProps\u003cDuck\u003e) {\n  const { selectors, creators, ducks: { counter1 } } = duck;\n  return (\n    \u003cdiv\u003e\n      counter1:\n      \u003cCounter duck={counter1} store={store} dispatch={dispatch} /\u003e\n      myself: total increment times: {selectors.total(store)} \u003cbr/\u003e\n      \u003cbutton onClick={()=\u003edispatch(creators.increment())}\u003e\n        Increment all\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## Run and connect to React (hook style)\n```js\nimport * as React from 'react'\nimport Counter from \"./Counter\";\nimport Duck from './RootDuck'\nimport { useDuck } from 'saga-duck'\n\nexport default function Root() {\n  const { duck, store, dispatch } = useDuck(Duck)\n  const { selectors, creators, ducks: { counter1 } } = duck;\n  return (\n    \u003cdiv\u003e\n      counter1:\n      \u003cCounter duck={counter1} store={store} dispatch={dispatch} /\u003e\n      myself: total increment times: {selectors.total(store)} \u003cbr/\u003e\n      \u003cbutton onClick={()=\u003edispatch(creators.increment())}\u003e\n        Increment all\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n## Helpers\n### useDuck\nConnect duck to react in hook style.\n```js\nfunction MyCmp(){\n  const { duck, store, dispatch } = useDuck(MyDuck)\n  const { selector, creators } = duck;\n  return \u003c\u003e{selector(store).xxx}\u003c/\u003e\n}\n```\n### purify\nmake React DuckComponent pure, only rerender when props and duck state changed.\n```javascript\nimport { purify } from 'saga-duck'\nexport default purify(function DuckComponent({ duck, store, dispatch }){ ... })\n```\n\n### memorize\nstabilize objects/functions reference, prevent React props unnecessary change. \n\n**With React 16, you can use `useMemo` or `useCallback` hooks instead**\n```javascript\nimport { memorize } from 'saga-duck'\nconst getHandler = memorize((duck, dispatch) =\u003e ()=\u003edispatch(duck.creators.bar()) )\n\nfunction Container(props){\n  const handler = gethandler(props)\n  return \u003cFoo handler={handler} /\u003e\n}\n```\n\n### reduceFromPayload / createToPayload\nCreate simple reducer / actionCreator\n```javascript\nimport { reduceFromPayload, createToPayload } from 'saga-duck'\n\nlet reducer = reduceFromPayload(types.SET_ID, 0)\n// equal to \nreducer = (state=0, action)=\u003e{\n  switch(action.type){\n    case types.SET_ID:\n      return action.payload\n    default:\n      return state\n  }\n}\n\nlet creator = createToPayload\u003cnumber\u003e(types.SET_ID)\n// equal to\ncreator = (id: number)=\u003e({ type: types.SET_ID, payload: id })\n```\n\n## Typescript support\nSee [Duck example](./examples/src/CounterDuck.ts) and [ComposableDuck example](./examples/src/RootDuck.ts), please use typescript **3.0+** for saga-duck 3.x, and typescript 2.6.1 for saga-duck 2.x.\n### types\n![ts hint of types](examples/ts-types.png)\n\n### state (generate from reducers)\n![ts hint of state](examples/ts-state.png)\n\n### selectors\n![ts hint of selectors](examples/ts-selectors.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrilluce%2Fsaga-duck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyrilluce%2Fsaga-duck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrilluce%2Fsaga-duck/lists"}