{"id":25724049,"url":"https://github.com/rinaldo/curried-reducer","last_synced_at":"2026-05-06T14:40:46.759Z","repository":{"id":142811563,"uuid":"373163025","full_name":"Rinaldo/curried-reducer","owner":"Rinaldo","description":"Generate type-safe action creators and a reducer from a set of curried functions","archived":false,"fork":false,"pushed_at":"2022-02-16T02:39:57.000Z","size":95,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-18T14:36:52.362Z","etag":null,"topics":[],"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/Rinaldo.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,"governance":null}},"created_at":"2021-06-02T12:39:19.000Z","updated_at":"2022-02-16T02:39:55.000Z","dependencies_parsed_at":"2023-09-24T06:33:01.951Z","dependency_job_id":null,"html_url":"https://github.com/Rinaldo/curried-reducer","commit_stats":{"total_commits":2,"total_committers":1,"mean_commits":2.0,"dds":0.0,"last_synced_commit":"58a7b0161227d0a749e9baf8cfeb0c06996e6a73"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Rinaldo/curried-reducer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fcurried-reducer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fcurried-reducer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fcurried-reducer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fcurried-reducer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Rinaldo","download_url":"https://codeload.github.com/Rinaldo/curried-reducer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Rinaldo%2Fcurried-reducer/sbom","scorecard":{"id":121054,"data":{"date":"2025-08-11","repo":{"name":"github.com/Rinaldo/curried-reducer","commit":"58a7b0161227d0a749e9baf8cfeb0c06996e6a73"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"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":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/2 approved changesets -- score normalized to 0","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":"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":-1,"reason":"No tokens found","details":null,"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":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md: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":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"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":"Vulnerabilities","score":0,"reason":"16 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"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-16T02:25:57.748Z","repository_id":142811563,"created_at":"2025-08-16T02:25:57.748Z","updated_at":"2025-08-16T02:25:57.748Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285068433,"owners_count":27109462,"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","status":"online","status_checked_at":"2025-11-18T02:00:05.759Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2025-02-25T21:18:56.538Z","updated_at":"2025-11-18T14:36:53.111Z","avatar_url":"https://github.com/Rinaldo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Curried Reducer\n\n`npm install curried-reducer`\n\nCurried Reducer generates type-safe action creators and a reducer from a set of curried functions. It works well with libraries like [Ramda](https://ramdajs.com/), [Immer](https://immerjs.github.io/immer/), and [Optix](https://www.npmjs.com/package/optix) to eliminate boilerplate and make even complex actions clear and concise.\n\n- **Simple yet powerful**: All the power of Redux without the boilerplate\n- **Type-safe**: Robust type checking\n- **Tiny**: \u003c 1kb gzipped, zero dependencies\n\n## Usage\n\nThe [`generate`](#generate) function generates action creators and a reducer from a map of action handlers. Each handler has the shape `(...args: any[]) =\u003e (state: State) =\u003e State`\n\n```javascript\nimport { generate } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice A\", age: 22 }\n\nconst { actions, reducer } = generate({\n    setName: (firstName, lastName) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName }),\n    setAge: age =\u003e state =\u003e ({ ...state, age }),\n    incrementAge: () =\u003e state =\u003e ({ ...state, age: state.age + 1 })\n})\n// the following action creators and a reducer are generated\nactions.setName(\"Bob\", \"B\")\nactions.setAge(44)\nactions.incrementAge()\n```\n\n### Namespacing actions\n\nHandlers can be namespaced for better organization\n\n```javascript\nimport { generate } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice A\", age: 22 }\n\nconst { actions, reducer } = generate({\n    setName: (firstName, lastName) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName }),\n    age: {\n        set: age =\u003e state =\u003e ({ ...state, age }),\n        increment: () =\u003e state =\u003e ({ ...state, age: state.age + 1 })\n    }\n})\n// the following action creators and a reducer are generated\nactions.setName(\"Bob\", \"B\")\nactions.age.set(44)\nactions.age.increment()\n```\n\n### Scoping actions\n\nNamespaces can be [`scoped`](#scope) allowing for simpler handlers within them\n\n```javascript\nimport { generate, scope } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice A\", age: 22 }\n\nconst { actions, reducer } = generate({\n    setFullName: (firstName, lastName) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName }),\n    age: scope(\"age\", {\n        set: age =\u003e () =\u003e age,\n        increment: () =\u003e age =\u003e age + 1\n    })\n})\n// the following action creators and a reducer are generated\nactions.setFullName(\"Bob\", \"B\")\nactions.age.set(44)\nactions.age.increment()\n```\n\n### Customizing action creators\nThe default action creator just passes its arguments through to the reducer, but sometimes an action creator needs to add something like an id or timestamp\n\nThe [`customPayload`](#customPayload) helper can customize the payload that's passed to the reducer\n\n```javascript\nimport { generate, customPayload } from \"curried-reducer\"\nimport { nanoid } from \"nanoid\"\n\nconst initialState = [{ content: \"Some content\", postId: \"A234B\" }]\n\nconst { actions, reducer } = generate({\n    addPost: customPayload(\n        content =\u003e ({ content, postId: nanoid() }),\n        post =\u003e posts =\u003e [...posts, post]\n    )\n})\n```\n\nThe [`customAction`](#customAction) helper allows for full customization of the action creatoer\n\n```javascript\nimport { generate, customAction } from \"curried-reducer\"\nimport { nanoid } from \"nanoid\"\n\nconst initialState = [{ content: \"Some content\", postId: \"A234B\" }]\n\nconst { actions, reducer } = generate({\n    addPost: customAction(\n        type =\u003e content =\u003e ({\n            type,\n            payload: { content, postId: nanoid() },\n            meta: { flagForSomeOtherPackage: true }\n        }),\n        action =\u003e posts =\u003e [...posts, action.payload]\n    )\n})\n```\n\n## API Reference\n\n### generate\n\nThe generate function returns `actions` and a `reducer` from a reducerMap and (optional) options argument.\n\nThe reducerMap argument is an object where each key is a handler or a reducerMap. Each handler has the shape `(...args: any[]) =\u003e (state: State) =\u003e State`.\n\nThe optional options argument has the shape `{ initialState?: State; prefix?: string }`. The initialState key sets the default value in the reducer and the prefix argument adds a prefix to every action type.\n\nExamples:\n\n```javascript\nimport { generate } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice A\", age: 22 }\n\n// basic usage\nconst { actions, reducer } = generate({\n    setName: (firstName, lastName) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName })\n})\nconst newState = reducer(initialState, actions.setName(\"Bob\", \"B\")) // { name: \"Bob B\", age: 22 }\n\n// with options\nconst result2 = generate({\n    setName: (firstName, lastName) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName })\n}, { initialState, prefix: \"user\" })\n\n\ntype State = typeof initialState\n\n// alternate TypeScript call signature to infer the type of state within each handler\nconst result3 = generate\u003cState\u003e()({\n    setName: (firstName: string, lastName: string) =\u003e state =\u003e ({ ...state, name: firstName + \" \" + lastName })\n})\n```\n\n### customPayload\n\nThe customPayload helper returns a handler with a customized payload from an actionCreator and payloadHandler argument.\n\nThe actionCreator argument has the shape `(...args: any[]) =\u003e any` and the handler has the shape `(payload: any) =\u003e (state: State) =\u003e State`. The return value of the actionCreator is effectively piped into the payloadHandler.\n\nExample:\n\n```javascript\nimport { generate, customPayload } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice\", age: 22 }\n\nconst { actions, reducer } = generate({\n    setName: customPayload(\n        (firstName, lastName) =\u003e firstName + \" \" + lastName,\n        fullName =\u003e state =\u003e ({ ...state, name: fullName })\n    )\n})\n```\n\n### customAction\n\nThe customAction helper returns a handler with a customized payload from a typeToAction and actionHandler argument.\n\nThe typeToAction argument takes in an action type and returns an action creator for actions of that type. The handler has the shape `(action: { type: string; [key: string]: any }) =\u003e (state: State) =\u003e State`. The final return value of the typeToAction is effectively piped into the actionHandler.\n\n*the action object must include the provided action type*\n\nExample:\n\n```javascript\nimport { generate, customAction } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice\", age: 22 }\n\nconst { actions, reducer } = generate({\n    setName: customAction(\n        type =\u003e (firstName, lastName) =\u003e ({ type, payload: firstName + \" \" + lastName }),\n        action =\u003e state =\u003e ({ ...state, name: action.payload })\n    ),\n    curriedSetName: customAction(\n        type =\u003e firstName =\u003e lastName =\u003e ({ type, payload: firstName + \" \" + lastName }),\n        action =\u003e state =\u003e ({ ...state, name: action.payload })\n    )\n})\nactions.setName(\"Alice\", \"A\")\nactions.curriedSetName(\"Alice\")(\"A\")\n```\n\n### scope\n\nThe scope helper takes a key and an reducerMap and returns a version of that reducerMap scoped to that key. It's similar in spirit to the combineReducers function from Redux.\n\nExmaples:\n\n```javascript\nimport { generate, scope } from \"curried-reducer\"\n\nconst initialState = {\n    user: {\n        name: \"Alice\",\n        age: 22\n    }\n}\n\nconst { actions, reducer } = generate({\n    user: scope(\"user\", {\n        setAge: age =\u003e user =\u003e ({ ...user, age })\n    })\n})\n\n\ntype State = typeof initialState\n\n// alternate TypeScript call signature to infer the type of state within each handler\nconst { actions, reducer } = generate\u003cState\u003e()({\n    user: scope\u003cState\u003e()(\"user\", {\n        setAge: age =\u003e user =\u003e ({ ...user, age })\n    })\n})\n```\n\n### bindActionCreators\n\nThe bindActionCreators helper takes in a map of action creators and a dispatch function. It returns a new map of action creators of the same shape bound to that dispatch function.\n\nSetting the optional third argument to true will make the action creators lazy.\n\nExamples:\n\n```jsx\nimport { useReducer, useMemo } from \"react\"\nimport { generate, bindActionCreators } from \"curried-reducer\"\n\nconst initialState = { color: \"gray\" }\n\nconst { actions, reducer } = generate({\n    setColor: color =\u003e state =\u003e ({ ...state, color })\n})\n\nconst Component1 = () =\u003e {\n    const [state, dispatch] = useReducer(reducer, initialState)\n    const boundActions = useMemo(() =\u003e bindActionCreators(actions, dispatch), [])\n    const { setColor } = boundActions\n\n    return (\n        \u003cdiv\u003e\n            \u003cdiv style={{ height: 50, backgroundColor: state.color }} /\u003e\n            \u003cbutton onClick={() =\u003e setColor(\"red\")}\u003eRed\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e setColor(\"blue\")}\u003eBlue\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e setColor(\"green\")}\u003eGreen\u003c/button\u003e\n        \u003c/div\u003e\n    )\n}\n\nconst Component2 = () =\u003e {\n    const [state, dispatch] = useReducer(reducer, initialState)\n    const lazyBoundActions = useMemo(() =\u003e bindActionCreators(actions, dispatch, true), [])\n    const { setColor } = lazyBoundActions\n\n    return (\n        \u003cdiv\u003e\n            \u003cdiv style={{ height: 50, backgroundColor: state.color }} /\u003e\n            \u003cbutton onClick={setColor(\"red\")}\u003eRed\u003c/button\u003e\n            \u003cbutton onClick={setColor(\"blue\")}\u003eBlue\u003c/button\u003e\n            \u003cbutton onClick={setColor(\"green\")}\u003eGreen\u003c/button\u003e\n        \u003c/div\u003e\n    )\n}\n\n```\n\n### asMap\n\nTypescript helper to infer the type of state within each handler. It's just the identity function.\n\n```typescript\nimport { asMap, generate } from \"curried-reducer\"\n\ninterface Person {\n    name: string\n    age: number\n}\n\n// Using the asMap helper so state is inferred as Person within the handler\nconst reducerMap = asMap\u003cPerson\u003e()({\n    setName: (name: string) =\u003e state =\u003e ({ ...state, name })\n})\nconst { actions, reducer } = generate(reducerMap)\n\n\n// Using the alternate call signature for generate so state is inferred as Person within the handler\n// asMap is not needed when using this alternate call signature\nconst { actions, reducer } = generate\u003cPerson\u003e()({\n    setName: (name: string) =\u003e state =\u003e ({ ...state, name })\n})\n```\n\n## Usage Notes\n\n### Pure and Impure handlers\n\nSince handlers are run in the reducer it is strongly recommended that they be pure. If things like timestamps or auto-generated ids are required, they should only be used within customPayload or customAction.\n\n```javascript\nimport { generate, customPayload } from \"curried-reducer\"\nimport { nanoid } from \"nanoid\"\n\nconst initialState = [{ content: \"Some content\", postId: \"A234B\" }]\n\nconst { actions, reducer } = generate({\n    addPost1: content =\u003e {\n        const post = { content, postId: nanoid() } // BAD: works but results in an impure reducer\n        return state =\u003e [...state, post]\n    },\n    addPost2: (content, postId = nanoid()) =\u003e state =\u003e [...state, { content, postId: nanoid() }], // BAD: works but results in an impure reducer\n    addPost3: customPayload(\n        content =\u003e ({ content, postId: nanoid() }), // GOOD: reducer is kept pure\n        post =\u003e state =\u003e [...state, post]\n    ),\n})\n```\n\n### Using the spread operator\n\nThe behavior of the default action creator is determined by the length of the handler function. Because of this the spread operator is not supported in handler arguments. For variadic action creators use either customPayload or customAction.\n\n```javascript\nimport { generate, customPayload } from \"curried-reducer\"\n\nconst interests = [\"JavaScript\", \"TypeScript\"]\n\nconst { actions, reducer } = generate({\n    addInterests1: (...interests) =\u003e state =\u003e [...state, ...interests], // ERROR: the spread operator is not supported in handler arguments, the interests argument will always be an empty array\n    addInterests2: interests =\u003e state =\u003e [...state, ...interests], // GOOD: this version takes an array of interests and then spreads them in the reducer\n    addInterests3: customPayload(\n        (...interests) =\u003e interests, // GOOD: the spread operator is supported in action creator arguments when using customPayload or customAction\n        interests =\u003e state =\u003e [...state, ...interests]\n    )\n})\n```\n\n### Curried Action Creators\n\nCurried action creators are supported when using customAction\n\n```javascript\nimport { generate, customAction } from \"curried-reducer\"\n\nconst initialState = { name: \"Alice\", age: 22 }\n\nconst { actions, reducer } = generate({\n    curriedSetName: customAction(\n        type =\u003e firstName =\u003e lastName =\u003e ({ type, payload: firstName + \" \" + lastName }),\n        action =\u003e state =\u003e ({ ...state, name: action.payload })\n    )\n})\nactions.curriedSetName(\"Alice\")(\"A\")\n```\n\n### Usage with curried utility functions\nActions can be made even more concise with the help of curried, data-last utility functions\n\n```javascript\nimport { generate } from \"curried-reducer\"\nimport { find, set, update } from \"optix\"\nimport { assoc, inc } from \"ramda\"\nimport { produce } from \"immer\"\n\nconst initialState = {\n    name: \"Alice A\",\n    age: 22,\n    posts: [{ title: \"My Time in Wonderland\", content: \"It all started when...\", postId: \"A234B\" }]\n}\n\n// the following sets of actions are equivalent\n// the scope helper would simplify these actions, it is omitted here to highlight the capabilities of the other libraries\nconst { actions, reducer } = generate({\n    name: {\n        setVanilla: name =\u003e state =\u003e ({ ...state, name }),\n        setWithOptix: set(\"name\"),\n        setWithRamda: assoc(\"name\"),\n        setWithImmer: name =\u003e produce(state =\u003e {\n            state.name = name\n        })\n    },\n    age: {\n        incrementVanilla: () =\u003e state =\u003e ({ ...state, age: state.age + 1 }),\n        incWithOptixRamda: () =\u003e update(\"age\")(inc),\n        incrementWithImmer: () =\u003e produce(state =\u003e {\n            state.age++\n        })\n\n    },\n    posts: {\n        editVanilla: (updatedContent, postId) =\u003e state =\u003e ({\n            ...state,\n            posts: state.posts.map(post =\u003e post.postId === postId\n                ? ({ ...post, content: updatedContent })\n                : post\n            )\n        }),\n        editWithOptix: (updatedContent, postId) =\u003e\n            set(\"posts\", find(post =\u003e post.postId === postId), \"content\")(content),\n        editWithImmer: (updatedContent, postId) =\u003e produce(state =\u003e {\n            const index = state.posts.findIndex(post =\u003e post.postId === postId)\n            state.posts[index].content = updatedContent\n        })\n    }\n})\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Fcurried-reducer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frinaldo%2Fcurried-reducer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frinaldo%2Fcurried-reducer/lists"}