{"id":21538918,"url":"https://github.com/nubescope/reduxable","last_synced_at":"2025-04-10T03:24:45.468Z","repository":{"id":57351926,"uuid":"76194018","full_name":"Nubescope/reduxable","owner":"Nubescope","description":"Reusable Redux, without boilerplate","archived":false,"fork":false,"pushed_at":"2017-10-03T19:07:35.000Z","size":456,"stargazers_count":27,"open_issues_count":10,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T04:43:29.485Z","etag":null,"topics":["component","no-boilerplate","redux","reusable"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/Nubescope.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-11T19:12:19.000Z","updated_at":"2023-05-13T14:33:48.000Z","dependencies_parsed_at":"2022-08-29T16:31:24.817Z","dependency_job_id":null,"html_url":"https://github.com/Nubescope/reduxable","commit_stats":null,"previous_names":["underscopeio/reduxable"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nubescope%2Freduxable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nubescope%2Freduxable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nubescope%2Freduxable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nubescope%2Freduxable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nubescope","download_url":"https://codeload.github.com/Nubescope/reduxable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248149681,"owners_count":21055803,"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":["component","no-boilerplate","redux","reusable"],"created_at":"2024-11-24T04:13:41.329Z","updated_at":"2025-04-10T03:24:45.443Z","avatar_url":"https://github.com/Nubescope.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Reduxable\n\nReusable Redux **without boilerplate**\n\nIt's strongly based on Redux so you should know how Redux works before start using this library. \n\n_To get more context on the problem this library solves take your time to read [this blog post](https://medium.com/underscopeio/a-solution-to-redux-biggest-pains-1ecd904e1a0b)._\n\n## Sections\n* [Installation](#installation)\n* [Motivation](#motivation)\n* [What is Reduxable?](#what-is-reduxable)\n* [Integration](#integration)\n* [Usage](#usage)\n* [FAQ](#faq)\n* [Examples](#examples)\n\n### Installation\n\nTo install the stable version:\n\n```bash\n$ npm install --save reduxable\n```\nor with Yarn\n```bash\n$ yarn add reduxable\n```\n\n### Motivation\n\nRedux is great! It solves a really hard problem: **state managament**.\n\nAnd it does it in an simple, easy to understand way, based in the following principles:\n\n\u003e The whole state of your app is stored in an object tree inside a single *store*.\n\n\u003e The only way to change the state tree is to emit an *action*, an object describing what happened.\n\n\u003e To specify how the actions transform the state tree, you write pure *reducers*.\n\n\nThis comes with a lot of benefits in terms of simplicity and tools, specially **great tools**.\n\nBut there are some issues that Redux does not solve:\n\n- It's really difficult to reuse the code due to the global scope: the action types could collide\n- We need to create a lot of boilerplate even for a tiny feature\n\nReduxable tackle both problems and aims to do it in an elegant way, **always honoring the 3 Redux principles** so we can keep using most of the tools we already have for Redux.\n\n### What is Reduxable\nReduxable is a library for creating reusable _state components_. We're convinced that thinking in components is very important; we love React for that reason: you end up with a more robust and easy to understand code. Reduxable enforces you to think this way for the state too.\n\nAll you need to care about is what really matters, the real logic: the state, the reducers and some selectors and methods to do async stuffs.\n\nInternally each action has a `scope` and each reducer will check for it. Also you can access to the state using `.state` which will give you the portion of the state for this _state component_.\n\n### Integration\nSince Reduxable is fully compatible with Redux, we recommend you to integrate it first and check everything is working well before start creating your Reduxable _state components_.\n\n#### With Redux\n\nIf you're already using Redux you must follow this two easy steps:\n  \n  1. In the file you create the store, import `createStore` from `reduxable` instead of `redux`.\n  ![Migrating to Reduxable - Modifying store.js](./.github/store.png)\n  \n  2. In the file you combine all the reducers, replace `combineReducers({...})` by `new Reduxable({...})`.\n  ![Migrating to Reduxable - Modifying reducers.js](./.github/reducers.png)\n  \n\nFinally check everything is still working and then add your new reduxable to the _**main reduxable**_ (see how to create a Counter Reduxable)\n\n![Migrating to Reduxable - Adding new reduxable in reducers.js](./.github/add-new-reduxable.png)\n\n#### If you're starting from scratch\nImport `createStore` from `reduxable` in the file you create the store.\n```js\nimport { createStore } from 'reduxable'\nimport mainReduxable from './reduxables'\n\nconst store = createStore(mainReduxable)\n```\n\nCreate a main file where you will combine all your reduxables (in this case `reduxables/index.js`)\n```js\nimport Reduxable from 'reduxable'\nimport Counter from './counter-reduxable'\n\nconst myApp = new Reduxable({\n  counter: new Counter()\n})\n\nexport default myApp\n```\n\n### Usage\n#### Creating a basic Reduxable\nFor a Reduxable you just need to define the initial `state` and the `reducers`. They are _statics_ so that can be reused across all the instances.\n```js\nimport Reduxable from 'reduxable'\n\nconst reducers = {\n  increment: (state) =\u003e state + 1,\n  decrement: (state) =\u003e state - 1,\n}\n\nclass Counter extends Reduxable {\n  constructor() {\n    super(0, reducers)\n  }\n}\n\nexport default Counter\n```\n\nTo use it just import it and create a new instance\n```js\nimport Counter from './counter'\nconst newCounter = new Counter()\n```\n\nGet the state of that counter using `state`\n```js\nnewCounter.state // =\u003e 0\n```\n\nCall the reducers as methods. The state will be bound internally, as Redux does.\n```js\nnewCounter.reducers.increment()\nnewCounter.state // =\u003e 1\n```\n\n#### Reducers alias\nIt's easy to define alias for the reducers so, for example, you call `counter.increment()` instead of `counter.reducers.increment()`. We prefer not to do it automatically to avoid _too magic_ stuff.\n```js\nclass Counter extends Reduxable {\n  constructor() {\n    super(0)\n  }\n\n  static reducers = {\n    increment: state =\u003e state + 1\n  }\n\n  increment = () =\u003e this.reducers.increment()\n}\n```\n\n#### Selectors\nSelectors are easier than ever. Taking advantage on Reduxable `state` method that will give you the scoped state you can define them as simple methods.\n```js\nclass DeepState extends Reduxable {\n  constructor() {\n    super({\n      some: {\n        deep: {\n          data: 'Gold'\n        }\n      }\n    })\n  }\n\n  static reducers = {\n    ...\n  }\n\n  getDeepData = () =\u003e {\n    return this.state.some.deep.data\n  }\n}\n\nconst deepState = new DeepState()\ndeepState.getDeepData() // =\u003e 'Gold'\n```\n\n#### Reducers with payload\nThe reducers have the following signature `reducer(state, payload)` where the payload can be any primitive type or plain object/array. You can pass a payload to your reducer calling the method with a parameter.\n\n```js\nclass Counter extends Reduxable {\n  constructor() {\n    super(0)\n  }\n\n  static reducers = {\n    add: (state, n) =\u003e state + n,\n    addAll: (state, numbers) =\u003e state + numbers.reduce((sum, n) =\u003e sum + n)\n  }\n}\n\nconst newCounter = new Counter()\nnewCounter.reducers.add(50)\nnewCounter.reducers.addAll([10, 10, 30])\n```\n\n#### Global reducers\nThe reducers have the following signature `reducer(state, payload)` where the payload can be any primitive type or plain object/array. You can pass a payload to your reducer calling the method with a parameter.\n\n```js\nclass Counter extends Reduxable {\n  constructor() {\n    super(0)\n  }\n\n  static reducers = {\n    increment: state =\u003e state + 1\n  }\n\n  static globalReducers = {\n    OLD_REDUX_ACTION: state =\u003e 1000\n  }\n}\n\nconst newCounter = new Counter()\n// The OLD_REDUX_ACTION reducer will be called if an action with type `OLD_REDUX_ACTION` is dispatched\ndispatch({ type: 'OLD_REDUX_ACTION' })\nnewCounter.state // =\u003e 1000\n```\n\n#### Async actions\nIf you want an _async action creator_ you can easily do it as a method that internally will call a reducer. You don't need any middleware, just use `promises` or `async/await` and then call your reducers.\n\n```js\nimport Reduxable from 'reduxable'\n\nclass Counter extends Reduxable {\n  constructor() {\n    super(0)\n  }\n\n  static reducers = {\n    increment: (state) =\u003e state + 1,\n    decrement: (state) =\u003e state - 1,\n  }\n\n  incrementWithPromises = () =\u003e {\n    fetch('http://should-increment')\n      .then(() =\u003e this.reducers.increment())\n      .catch(() =\u003e this.reducers.decrement())\n  }\n\n  incrementWithAsyncAwait = async () =\u003e {\n    try {\n      await fetch('http://should-increment')\n      this.reducers.increment()\n    } catch() {\n      this.reducers.decrement()\n    }\n  }\n}\n\nconst newCounter = new Counter()\nnewCounter.incrementWithPromises()\nnewCounter.incrementWithAsyncAwait()\n```\n\n### FAQ\n#### Can I use the Redux Dev-Tools and other middlewares/enhancers?\nYes. Since Reduxable uses Redux internally and holds the 3 Redux principles, most of the middlewares and enhancers will work out of the box.\nIf you find one that doesn't please create an issue.\n\n### Examples\n\n* [Counter](https://github.com/underscopeio/reduxable/tree/master/examples/counter)\n* [Todos](https://github.com/underscopeio/reduxable/tree/master/examples/todos)\n* [Multiple Todos](https://github.com/underscopeio/reduxable/tree/master/examples/multiple-todos)\n\n### Change Log\n\nThis project adheres to [Semantic Versioning](http://semver.org/).\nEvery release, along with the migration instructions, is documented on the Github [Releases](https://github.com/underscopeio/reduxable/releases) page.\n\n### License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnubescope%2Freduxable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnubescope%2Freduxable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnubescope%2Freduxable/lists"}