{"id":17698570,"url":"https://github.com/iamogbz/react-ducks","last_synced_at":"2026-02-03T14:31:54.152Z","repository":{"id":37095565,"uuid":"292454872","full_name":"iamogbz/react-ducks","owner":"iamogbz","description":"🦆 Ducks architecture using native react features without redux framework","archived":false,"fork":false,"pushed_at":"2024-09-26T10:58:10.000Z","size":70759,"stargazers_count":2,"open_issues_count":7,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-20T20:41:25.292Z","etag":null,"topics":["immer","react","react-ducks","react-hooks","react-redux","react-redux-tutorial","redux","redux-applymiddleware"],"latest_commit_sha":null,"homepage":"https://ogbizi.com/react-ducks/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamogbz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-09-03T03:20:58.000Z","updated_at":"2024-06-26T10:53:45.000Z","dependencies_parsed_at":"2024-04-15T11:32:16.354Z","dependency_job_id":"74ca41f9-9b32-4d55-9a4b-fdaa4f5c3291","html_url":"https://github.com/iamogbz/react-ducks","commit_stats":{"total_commits":1188,"total_committers":3,"mean_commits":396.0,"dds":0.05555555555555558,"last_synced_commit":"c49ba6e20a2dfc2966d118913bff28eb7aec322e"},"previous_names":["iamogbz/react-duck"],"tags_count":25,"template":false,"template_full_name":"iamogbz/node-js-boilerplate","purl":"pkg:github/iamogbz/react-ducks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamogbz%2Freact-ducks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamogbz%2Freact-ducks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamogbz%2Freact-ducks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamogbz%2Freact-ducks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamogbz","download_url":"https://codeload.github.com/iamogbz/react-ducks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamogbz%2Freact-ducks/sbom","scorecard":{"id":477856,"data":{"date":"2025-08-11","repo":{"name":"github.com/iamogbz/react-ducks","commit":"3fa279b5ff49862146ccb81a7b448ad7577e85e9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/dependabot.yml:1","Warn: no topLevel permission defined: .github/workflows/nodejs.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":-1,"reason":"Found no human activity in the last 30 changesets","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/dependabot.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/dependabot.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/dependabot.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/dependabot.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/nodejs.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/nodejs.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/nodejs.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/nodejs.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/iamogbz/react-ducks/nodejs.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/nodejs.yml:29","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: The Unlicense: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"17 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-x4c5-c7rf-jjgv","Warn: Project is vulnerable to: GHSA-h5c3-5r3r-rr8q","Warn: Project is vulnerable to: GHSA-rmvr-2pp2-xj38","Warn: Project is vulnerable to: GHSA-xx4v-prfh-6cgc","Warn: Project is vulnerable to: GHSA-wf5p-g6vw-rhxx","Warn: Project is vulnerable to: GHSA-jr5f-v2jv-69x6","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-f5x3-32g6-xq36","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-4vvj-4cpr-p986"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T15:48:52.580Z","repository_id":37095565,"created_at":"2025-08-19T15:48:52.580Z","updated_at":"2025-08-19T15:48:52.580Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29047565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T10:09:22.136Z","status":"ssl_error","status_checked_at":"2026-02-03T10:09:16.814Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["immer","react","react-ducks","react-hooks","react-redux","react-redux-tutorial","redux","redux-applymiddleware"],"created_at":"2024-10-24T15:10:11.090Z","updated_at":"2026-02-03T14:31:54.136Z","avatar_url":"https://github.com/iamogbz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React Ducks\n\n[![NPM badge](https://img.shields.io/npm/v/react-ducks)](https://www.npmjs.com/package/react-ducks)\n[![Dependabot badge](https://badgen.net/github/dependabot/iamogbz/react-ducks/?icon=dependabot)](https://app.dependabot.com)\n[![Dependencies](https://img.shields.io/librariesio/github/iamogbz/react-ducks)](https://libraries.io/github/iamogbz/react-ducks)\n[![Build Status](https://github.com/iamogbz/react-ducks/workflows/Build/badge.svg)](https://github.com/iamogbz/react-ducks/actions)\n[![Coverage Status](https://coveralls.io/repos/github/iamogbz/react-ducks/badge.svg?branch=master)](https://coveralls.io/github/iamogbz/react-ducks?branch=master)\n\nImplement ducks in React following the redux pattern but using React Context.\n\nUses [`immer`][immer-intro] to wrap reducers when creating, ensuring atomic state mutations.\n\n## Usage\n\nCreate the ducks for each slice of application logic.\n\n```js\n// duck/counter.js\nexport default createDuck({\n  name: \"counter\",\n  initialState: 0,\n  reducers: {\n    increment: (state) =\u003e state + 1,\n  },\n  actionMapping: { otherActionType: \"increment\" },\n  selectors: { current: (namespacedState) =\u003e namespacedState[\"counter\"] },\n});\n```\n\n**Note**: The `current` selector is just an example. In an actual implementation it would be made redundant by `$` which is created by default for all ducks to fetch the namespaced state.\n\n```js\ncounterDuck.selectors.$(state);\n// this is equivalent to (rootState) =\u003e rootState[counterDuck.name]\n```\n\nCreate the root/global duck as a combination of all other ducks.\n\n```js\n// duck/index.js\nexport default createRootDuck(counterDuck, otherDuck);\n```\n\nCreate the global context.\n\n```js\n// context.js\nexport default createContext(\n  rootDuck.reducer,\n  rootDuck.initialState,\n  enhancer,\n  \"ContextName\",\n  useAsGlobalContext\n);\n```\n\n**Note**: The `enhancer` may be optionally specified to enhance the context with third-party capabilities such as middleware, time travel, persistence, etc. The only context enhancer that ships with Ducks is [applyMiddleware](#applyMiddlewaremiddlewares).\n\n**Note**: The `useAsGlobalContext` i.e. `global` option; allows for setting a default context that is used by the [`useDispatch`](#useDispatchactionCreatorContext) and [`useSelector`](#useSelectorselectorContext) hooks when no `Context` is supplied. This is useful when creating the context that will be used with the root provider.\n\nUse the state and actions in your component.\n\n```jsx\n// app.jsx\nexport default function App(props) {\n  const { state, dispatch } = React.useContext(Context);\n  const count = counterDuck.selectors.$(state);\n  const increment = React.useCallback(\n    () =\u003e dispatch(counterDuck.actions.increment()),\n    [dispatch]\n  );\n  return (\n    \u003cdiv\u003e\n      Count: \u003cspan\u003e{count}\u003c/span\u003e\n      \u003cbutton onClick={increment} /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n**Note**: The use of `React.useContext` can be replaced with a combination of [`useDispatch`](#useDispatchactionCreatorContext) and [`useSelector`](#useSelectorselectorContext) hooks.\n\n```jsx\n// app.jsx\n...\n  const count = useSelector(counterDuck.selectors.$, Context);\n  const increment = useDispatch(counterDuck.actions.increment, Context);\n...\n```\n\n**Note**: This is equivalent to the class component described below.\n\n```jsx\n// app.jsx\nexport default class App extends React.PureComponent {\n  static contextType = Context;\n\n  render() {\n    const { state } = this.context;\n    return (\n      \u003cdiv\u003e\n        Count: \u003cspan\u003e{counterDuck.selectors.$(state)}\u003c/span\u003e\n        \u003cbutton onClick={this.increment} /\u003e\n      \u003c/div\u003e\n    );\n  }\n\n  increment = () =\u003e {\n    this.context.dispatch(counterDuck.actions.increment());\n  };\n}\n```\n\nWrap the application in the root provider to handle state changes.\n\n```js\n// index.jsx\nconst rootElement = document.getElementById(\"root\");\nconst Provider = createRootProvider(Context);\nReactDOM.render(\n  \u003cProvider\u003e\n    \u003cApp /\u003e\n  \u003c/Provider\u003e,\n  rootElement\n);\n```\n\n**Note**: `createRootProvider` is just a helper and can be replaced, with the functional difference highlighted below.\n\n```js\n// index.jsx\nconst rootElement = document.getElementById(\"root\");\nReactDOM.render(\n  \u003cProvider Context={Context}\u003e\n    \u003cApp /\u003e\n...\n```\n\nA side benefit to scoping the context state to the provider is allowing multiple entire apps to be run concurrently.\n\n### applyMiddleware(...middlewares)\n\nThis takes a variable list of middlewares to be applied.\n\n#### Example: Custom Logger Middleware\n\n```js\n// context.js\nfunction logger({ getState }) {\n  // Recommend making the returned dispatch method asynchronous.\n  return (next) =\u003e async (action) =\u003e {\n    console.log(\"will dispatch\", action);\n    // Call the next dispatch method in the middleware chain.\n    const returnValue = await next(action);\n    // Resolving the result of the next dispatch allows the referenced\n    // state to be updated by `React.useReducer` and available to get.\n    console.log(\"state after dispatch\", getState());\n    // This will likely be the action itself, unless\n    // a middleware further in chain changed it.\n    return returnValue;\n  };\n}\n\nexport default createContext(..., applyMiddleware(logger));\n```\n\nSee [redux applyMiddleware][redux-applymiddleware] for more documentation.\n\n## Demo\n\nAs a proof of concept see the converted sandbox app from the react-redux basic tutorial below.\n\n- With redux [example][react-redux-tutorial]\n- Without redux [example][react-ducks-no-redux]\n- With redux saga [example][react-ducks-saga]\n\n## References\n\nLots of inspiration from the following tools\n\n- [Redux Toolkit][redux-toolkit]\n- [React Redux][react-redux]\n\n[immer-intro]: https://medium.com/hackernoon/introducing-immer-immutability-the-easy-way-9d73d8f71cb3\n[proposal-observable]: https://github.com/tc39/proposal-observable\n[react-ducks-no-redux]: https://codesandbox.io/s/todo-app-without-redux-9yc57\n[react-ducks-saga]: https://github.com/iamogbz/example-react-duck-saga\n[react-redux]: https://react-redux.js.org/api/connect\n[react-redux-tutorial]: https://react-redux.js.org/introduction/basic-tutorial\n[redux-applymiddleware]: https://redux.js.org/api/applymiddleware#applymiddlewaremiddleware\n[redux-toolkit]: https://redux-toolkit.js.org/api/createReducer\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamogbz%2Freact-ducks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamogbz%2Freact-ducks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamogbz%2Freact-ducks/lists"}