{"id":19898926,"url":"https://github.com/nehle/horux","last_synced_at":"2025-05-02T22:31:37.842Z","repository":{"id":17178819,"uuid":"81293200","full_name":"Nehle/horux","owner":"Nehle","description":"A collection of utility belt functions for composing reducers for redux","archived":false,"fork":false,"pushed_at":"2024-07-24T08:33:17.000Z","size":391,"stargazers_count":10,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-20T22:04:36.170Z","etag":null,"topics":["composing-reducers","functional-js","higher-order-reducers","reducer","redux"],"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/Nehle.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":"2017-02-08T05:55:56.000Z","updated_at":"2024-07-24T08:32:42.000Z","dependencies_parsed_at":"2023-02-16T07:00:50.762Z","dependency_job_id":"a16a306b-e08e-4d3c-93e3-f1c7816cc3ef","html_url":"https://github.com/Nehle/horux","commit_stats":{"total_commits":76,"total_committers":5,"mean_commits":15.2,"dds":0.5657894736842105,"last_synced_commit":"f977cb8b5fdaa8ad3f700d2c40bc936dc6767572"},"previous_names":["nehle/higher-order-reducers"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nehle%2Fhorux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nehle%2Fhorux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nehle%2Fhorux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nehle%2Fhorux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nehle","download_url":"https://codeload.github.com/Nehle/horux/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252116359,"owners_count":21697363,"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":["composing-reducers","functional-js","higher-order-reducers","reducer","redux"],"created_at":"2024-11-12T20:06:08.415Z","updated_at":"2025-05-02T22:31:35.249Z","avatar_url":"https://github.com/Nehle.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# horux\n\nHorux (**h**igher **or**der red**ux**) is a simple utility belt library for building and composing redux reducers using higher order functions.\n\nfor examples, see [EXAMPLES.md](EXAMPLES.md)\n\n## installation\n\nFrom command line, call `yarn add horux` or `npm install --save horux`\n\n## motivation\n\nThis project has sprung from the work I've done with redux and more and more complex reducers, where\nI've noticed a lot of boilerplate simple repeating itself. Despite the clean and simple philosophies\nof redux, I often ran into code like\n\n```js\nconst someReducer = (state = DEFAULT_STATE, action) {\n  switch(action.type) {\n    case TYPE1:\n    case TYPE2:\n      // Some specific logic here\n    case TYPE3:\n      // other logic\n    default:\n      return state;\n  }\n}\n```\nSometimes with so many `case` calls that `eslint` would complain about the complexity of the \"simple\"\nreducer.\n\nSo, inspired by how `recompose` uses higher-level-components to elegantly compose react components,\nI started experimenting with trying to extract some functional patterns from the above code. The result\nis the groundwork of this library. In the specific case above, it would be rewritten as\n\n```js\nimport { compose, withDefault, cloneState, mapByType } from \"horux\";\nconst someReducer = compose([\n    withDefault(DEFAULT_STATE)),\n    cloneState,\n    mapByType({\n      TYPE1: () =\u003e {},\n      TYPE2: () =\u003e {},\n      TYPE3: () =\u003e {}\n    })\n]);\n```\n\nIs that better? I'm not sure. I think it's prettier, but it might be slower. Regardless I felt that\nthis was a fun project to share with the world, if anybody cares. I will probably continue to code\nlike this until somebody proves to me that it's objectively worse than the alternative.\n\n## functions\n\n\n### `compose(reducers)`\nCreates a reducer that generates the state by calling the supplied reducers in order, with the output of the previous reducer as the input for the next, without changing the action. You can also accept a reference to the next reducer as a third `next` parameter. If accepted, `compose` will not automatically continue, instead you have to manually continue the chain by calling `next(nextState)` to call the next reducer with the new state and the original action.\n\n```js\nimport { compose } from \"horux\";\nconst add = (state, action) =\u003e state + action.value;\nconst addAndContinue = (state, action, next) =\u003e next(state + action.value);\nconst stop = (state, action, next) =\u003e state;\nconst reducer = compose([\n  add, // This reducer does not care for `next`, so compose continues\n  addAndContinue, // This reducer explicitly calls `next`, so compose continues\n  stop, // This reducer takes `next`, but doesn't call it, so the composition stops here\n  add // Never called\n]);\nreducer(1, {value: 2}) //5\n```\n\nThis allows us to cleanly create middleware reducers, that can work with the state without effecting the domain logic of our other reducers. For example, we can add functionality for logging and error handling thusly.\n\n```ts\nimport { compose } from \"horux\";\nimport { logger } from \"./logger\";\nimport { reducer } from \"./reducer\";\n\nconst reduxLogger = (state, action, next) =\u003e {\n  logger.debug(`pre-reduce: ${action} - ${state}`);\n  const nextState = next(state);\n  logger.debug(`post-reduce: ${action} - ${nextState}`);\n  return nextState;\n}\n\nconst errorHandler = (state, action, next) =\u003e {\n  const originalState = {...state};\n  try {\n    return next(state);\n  } catch (e) {\n    logger.error(`failed reducing ${originalState} from \"${action}\"`, e);\n    return originalState;\n  }\n}\n\nexport const compositeReducer = compose([errorHandler, reduxLogger, reducer]);\n```\n\n### `withDefault(defaultState)`\nReturns a reducer that returns the supplied `defaultState` if the `state` its supplied is `undefined`\n\n```js\nimport { withDefault } from \"horux\";\nconst reducer = withDefault(2);\nreducer(); //2\nreducer(1); //1\n```\n\n### `cloneState`\nReturns a deep clone of the supplied state. It is literally just an alias for\n`JSON.parse(JSON.stringify(state))`, which is by far the fastest way to perform that operation.\nHowever, it only works with plain objects (and arrays of them), and does not work with object instances,\nfunctions, regexes et cetera. If something like that is required, consider either implementing your own\nclone function that is built to clone your specific state as quickly as possible, or use something\nlike `lodash.cloneDeep`, but be aware of the performance implications\n\n### `mapByType(reducerMap)`\nReturn a reducer that maps action types to specific reducer functions, and returns the result\n(or the original state if the map does not contain the key)\n\n```js\nimport { mapByType } from \"horux\";\nconst reducer = mapByType({\n  'ACTION_ONE': (state, action) =\u003e action.value,\n  'ACTION_TWO': () =\u003e 2\n});\nreducer('', {type: 'ACTION_ONE', value: 'hello'}); //'hello'\nreducer('', {type: 'ACTION_TWO'}); //2\nreducer('state', {type: 'ACTION_THREE'}) //'state'\n```\n\n### `mergeStates(reducer)`\nMerges the keys returned by the reducer into the current state instead of replacing it entirely\n\n```js\nimport { mergeStates } from \"horux\";\nconst animal = (state, action) =\u003e {noise: action.noise, feet: action.feet};\nconst reducer = mergeStates(animal);\nreducer({name: \"duck\", noise: \"quack\"}, {noise: \"moo\", feet: 4}); //{name: \"duck\", noise: \"moo\", feet: 4}\n```\n\n### `nextIf(predicate)`\n\nMeant to be used with `compose`. Creates a reducer that will only call `next` if the supplied predicate (called with `state` and `action`) returns truthy. Otherwise stops the chain with the value it was supplied.\n\n```js\nimport { compose, nextIf } from \"horux\";\nconst add = (state, action) =\u003e state + action.value;\nconst onlyIfEven = nextIf(state, action) =\u003e (action.value % 2 === 0);\n\nconst reducer = compose([\n  onlyIfEven,\n  add\n]);\n\nreducer(1, {value: 1}) //1\nreducer(1, {value: 2}) //3\n```\n\n### `nextIfType(allowedTypes)`\n\nMeant to by used with `compose`. A special case of the `nextIf` method that only proceeds if the action `type` field matches a value in the supplied array.\n\n```js\nimport { compose, nextIfType } from \"horux\";\nconst add = (state, action) =\u003e state + action.value;\nconst onlyIfAdd = nextIfType([\"ADD_VALUE\"])\n\nconst reducer = compose([\n  onlyIfAdd,\n  add\n]);\n\nreducer(1, {value: 1, type: \"SUBTRACT_VALUE\"}) //1\nreducer(1, {value: 2, type: \"ADD_VALUE\"}) //3\n```\n\n## license\nSee [LICENSE.md](./LICENSE.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnehle%2Fhorux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnehle%2Fhorux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnehle%2Fhorux/lists"}