{"id":28092979,"url":"https://github.com/redux-definitions/redux-definitions","last_synced_at":"2025-05-13T13:59:00.210Z","repository":{"id":57350549,"uuid":"127349501","full_name":"redux-definitions/redux-definitions","owner":"redux-definitions","description":"🥒 Define and share reusable slices of Redux.","archived":false,"fork":false,"pushed_at":"2019-02-10T03:56:13.000Z","size":28058,"stargazers_count":28,"open_issues_count":8,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-24T16:04:33.460Z","etag":null,"topics":["enterprise","javascript","react","reducers","redux","repl","scaling","ui"],"latest_commit_sha":null,"homepage":"","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/redux-definitions.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":null,"security":null,"support":null}},"created_at":"2018-03-29T21:30:48.000Z","updated_at":"2022-05-28T05:34:48.000Z","dependencies_parsed_at":"2022-09-15T01:44:58.766Z","dependency_job_id":null,"html_url":"https://github.com/redux-definitions/redux-definitions","commit_stats":null,"previous_names":["redux-enterprise/redux-enterprise"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redux-definitions%2Fredux-definitions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redux-definitions%2Fredux-definitions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redux-definitions%2Fredux-definitions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redux-definitions%2Fredux-definitions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redux-definitions","download_url":"https://codeload.github.com/redux-definitions/redux-definitions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253773923,"owners_count":21962195,"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":["enterprise","javascript","react","reducers","redux","repl","scaling","ui"],"created_at":"2025-05-13T13:58:59.261Z","updated_at":"2025-05-13T13:59:00.200Z","avatar_url":"https://github.com/redux-definitions.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cbr\u003e\n\u003cp align=\"center\"\u003e\u003cimg height=\"55px\" src=\"https://raw.githubusercontent.com/redux-definitions/redux-definitions/master/images/logo-pad-right.png\" alt=\"🚀\"\u003e\u003c/p\u003e\n\u003ch1 align=\"center\"\u003e\n  Redux Definitions\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://travis-ci.org/redux-definitions/redux-definitions\"\u003e\u003cimg src=\"https://img.shields.io/travis/redux-definitions/redux-definitions/master.svg\" alt=\"travis\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/redux-definitions\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/redux-definitions.svg\" alt=\"npm version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://spectrum.chat/redux-enterprise\"\u003e\u003cimg src=\"https://withspectrum.github.io/badge/badge.svg\" alt=\"Join the community on Spectrum\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/redux-definitions\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/redux-definitions.svg\" alt=\"npm downloads\"\u003e\u003c/a\u003e\n  \u003ca href=\"http://www.typescriptlang.org/index.html\"\u003e\u003cimg src=\"https://badges.frapsoft.com/typescript/version/typescript-next.svg\" alt=\"Typescript - Next\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n```sh\nyarn add redux-definitions\n```\n\n**TLDR - Define and share reusable slices of Redux.**\n\nCommon reducer patterns always get recycled, write them once and then never repeat yourself again! Use [definitions](#reducer-definitions) to automatically generate namespaced reducers, actions, and selectors for a predefined slice of functionality.\n\n\u003e _The example below implements the Redux code necessary for a basic shopping experience in 12 lines of code. This is achieved by leveraging an existing Collection definition and creating a custom Cart definition._\n```js\nimport { createReducers, createDefinition, Definitions } from 'redux-definitions'\nconst { Collection } = Definitions\n\nconst Cart = createDefinition({\n  defaultState: [],\n  reducers: {\n    addItem: (state, { payload }) =\u003e state.push(payload),\n    clearItems: () =\u003e [],\n  },\n  selectors: {\n    getTotal: (state) =\u003e state.reduce((total, item) =\u003e total + item.price, 0)\n  }\n})\n\nconst { actions, reducers, selectors } = createReducers({\n  shopping: {\n    items: Collection,\n    cart: Cart\n  }\n})\n```\n**BONUS**\n\nUse the browser console to call actions and selectors against the running Redux application.\n  \n\u003cimg  width=\"100%\" src=\"https://raw.githubusercontent.com/redux-definitions/redux-definitions/master/images/repl-and-app-small.gif\" /\u003e\n\n# Table of Contents\n+ [**Overview**](#overview)\n  + [Objective](#objective)\n  + [Whats included?](#whats-included)\n+ [**Installation**](#installation)\n+ [**Concepts**](#concepts)\n  + [createReducers](#createreducers)\n  + [Actions](#actions)\n  + [Selectors](#selectors)\n  + [REPL](#redux-repl)\n  + [InitialState](#initialstate)\n + [**Reducer Definitions**](#reducer-definitions)\n   + [Field](#field)\n   + [Flag](#flag)\n   + [Record](#record)\n   + [Collection](#collection)\n   + [Index](#index)\n   + [Custom Reducer Definitions](#custom-reducer-definitions)\n+ [**Roadmap**](#roadmap)\n+ [**Appendix**](#appendix)\n  + [Typescript](#typescript)\n  + [Examples](#examples)\n  + [FAQ](#faq)\n  + [Contributing](#contributing)\n \n# Overview\nInspired by lessons learned building countless UIs, Redux Definitions is a library that **abstracts common Redux reducer patterns into reusable definitions** that can be shared and used to generate standardized and logically related actions, reducers, and selectors.\n\n\u003e Redux Definitions is 100% compatible with any existing Redux-based project.\n\n## Objective\nTo help organizations scale development, maintainability, and velocity on Redux-based projects.\n\n## Whats included?\n- Library of reusable reducer definitions (Flag, Field, Collection, Index, etc)\n- Automatically generated actions, reducers, and selectors.\n- In-browser Redux-REPL for interacting with running application.\n\n\n## Installation\n```sh\nyarn add redux-definitions\n```\n\n# Concepts\n\n## createReducers\nAs shown above your core application state is described using a library of [reducer definitions](#reducer-definitions). `createReducers` takes these definitions and creates reducers for you with corresponding actions and selectors.\n```js\nimport { createReducers, Definitions } from 'redux-definitions'\nconst { Collection, Flag, Field, Index } = Definitions\n\nconst { actions, reducers, selectors } = createReducers({\n  todoList: {\n    todos: Collection,\n    completedIds: Index,\n    selectedIds: Index\n  },\n  todoEditor: {\n    isEditing: Flag,\n    editingId: Field\n  }\n})\n```\n\u003e Each top-level key in the `createReducers` schema generates a separate reducer.\n\n## Actions\nAction creator functions are returned from `createReducers` calls as `actions`. The reducer definition determines what actions are available. For example a `Collection` has actions `create`, `upsert`, `remove`, `set`, `reset`, `clear`. Learn more about what actions are available and what their expected payloads look like the [reducer definitions](#reducer-definitions) section.\n\n```js\nconst { todoList, todoEditor } = actions\n\ntodoList.todos.create({ id: '1', message: 'Do thee laundry' })\n// { type: 'todos/create', payload: { id: '1', message: 'Do thee laundry' } }\n\ntodosList.todos.update({ id: '1', message: 'Do the laundry' })\n// { type: 'todos/update', payload: { id: '1', message: 'Do the laundry' } }\n\ntodoEditor.editingId.set('1')\n// { type: 'todoEditor/editingId/set', payload: '1' }\n\ntodoList.todos.remove('1')\n// { type: 'todos/remove', payload: '1' }\n\ntodoList.selectedIds.add('1')\n// { type: 'selected/add', payload: '1' } }\n\ntodoList.selectedIds.clear()\n// { type: 'selected/clear' }\n```\n\n## Selectors\nSelectors are also returned from `createReducers`. For example a `Collection` has `all`, `find`, `ids`, and `count`:\n```js\nconst { todoList, todoEditor } = selectors\n\ntodoList.todos.all(state) // returns a collection of todos\ntodoList.todos.find(state, { id }) // returns a todo with matching `id`\ntodoList.todos.ids(state) // returns an array of ids\n```\n\n\u003e These selectors are perfect for feeding into [Reselect](https://github.com/reactjs/reselect)\n\n## Redux-REPL\nWhen in dev-mode Redux Definitions automatically provides a REPL-like experience in the browser console for dispatching pre-bound actions and selectors. Actions and selectors from all `createReducers` calls are available in the REPL.\n\n\u003cimg  width=\"100%\" src=\"https://raw.githubusercontent.com/redux-definitions/redux-definitions/master/images/repl-and-app-small.gif\" /\u003e\n\n\u003e For your convenience unlike normal actions and selectors, calls to actions and selectors in the browser console are pre-bound to `store.dispatch` and `store.getState`. Remember, only in the console!\n\nTo setup the REPL, import `startRepl` and call it on your project's `store` object.\n```js\nimport { startRepl } from 'redux-definitions'\n\n...\n\nconst store = createStore(rootReducer, initialState, applyMiddleware(..))\n\nstartRepl(store)\n```\n\u003e Note: when server-side rendering this call will be a no-op.\n\n## InitialState\nAll reducer definitions accept `initialState` values.\n```js\nimport { createReducers, Definitions } from 'redux-definitions'\nconst { Collection, Flag, Field } = Definitions\n\nconst { reducers } = createReducers({\n  todoList: {\n    todos: Collection({\n      initialState: [{ id: '1', message: 'Do the laundry' }]\n    }),\n    completedIds: Index({\n      initialState: ['1']\n    }),\n    selectedIds: Index\n  },\n  todoEditor: {\n    isEditing: Flag({\n      initialState: true\n    }),\n    editingId: Field({\n      initialState: 'fooId'\n    })\n  }\n})\n```\n\n# Reducer Definitions\nRedux Definitions provides an assortment of reducer definitions that can be found by importing the `Definitions` object. Reducer definitions aim to be low-level enough to be generic but high level enough to abstract state patterns common to all applications.\n\n## Field\nField creates a simple reducer that stores any value and comes with action types that set and clear.\n\n### Actions\n`set(payload: any)`\nSets Field to payload value.\n\n`clear(void)`\nClears current Field state.\n\n### Selectors\n`get(state: {}): any`\nReturns the Field state.\n\n`isSet(state: {}): boolean`\nReturns a boolean specifying whether a Field value is set.\n\n## Record\nRecord creates a reducer that stores an object. The object can be set or updated (similar to setState).\n\n### Actions\n`set(payload: {})`\nSets Record state to payload object.\n\n`update(payload: {})`\nMerges payload object into the current Record state.\n\n`clear(void)`\nClears Record and sets it to an empty object.\n\n### Selectors\n`get(state: {}, keys?: string|string[]): {}`\nReturns the Record object, optionally takes keys to return a subset of the Record.\n\n`keys(state: {}): string[]`\nReturns an array of the Record's existing key names.\n\n## Flag\nFlag creates a reducer that stores a boolean value and comes with actions for setting and toggling.\n\n### Actions\n`set(payload?: boolean)`\nSets Flag to true or to the optional payload's boolean value.\n\n`unset(void)`\nSets Flag to false.\n\n`toggle(void)`\nToggles Flag value.\n\n### Selectors\n`get(state: {}): boolean`\nReturns the current Flag value.\n\n## Collection\nCollection creates a reducer that stores `Entities`. `Entities` are objects with `id` properties. Entities can take any form as long as they at least have an id. \n\n`Id = string`\n`Entity = { id: Id }`\n\n### Actions\n`set(payload: Entity[])`\nTakes an array of entities. Resets entire Collection to the payload of entities.\n\n`reset(void)` \nResets the entire Collection to empty.\n\n`create(payload: Entity)` \nTakes an Entity and adds it to the Collection. Warning will be logged if an entity with the `id` already exists.\n\n`update(payload: Entity)`\nTakes an Entity and updates it in the Collection. Entity is not added unless an entity with the `id` already exist.\n\n`upsert(payload: Entity)`\nTakes an Entity and updates it in the Collection. The entity will be added if an entity with the `id` does not exist.\n\n`remove(payload: Id|Id[])`\nTakes an Id and removes any existing entity with the `id`.\n\n### Selectors\n`all(state: {}): Entity[]`\nReturns array of entities.\n\n`ids(state: {}): Id[]`\nReturns array of ids.\n\n`find(state: {}, params: { id: Id }): Entity`\nReturns entity that matches id parameter.\n\n`count(state: {}) =\u003e number`\nReturns the number of entities in the Collection.\n\n`get(state: {}) =\u003e Normalized`\nReturns the full underlying data structure which takes the form: `{ ids, entities }` where `ids` is an array of unique `id` keys and `entities` is an `id`-based lookup map.of ids and entities.\n\n## Index\nIndex creates a reducer that stores a unique set of ids. Ids can be added, removed, and toggled. An Index is perfect for \n\n`Id = string`\n\n### Actions\n`set(payload: Id[])`\nTakes an array of identifiers. Resets entire Index to the payload.\n\n`reset(void)`\nResets entire Index to empty.\n\n`toggle(payload: Id)`\nTakes an identifier and toggles its presence in the Index.\n\n`add(payload: Id|Id[])`\nTakes an identifier(s) and ensures its presence in the Index.\n\n`remove(payload: Id|Id[])`\nTakes an identifier(s) and ensures its removal from the Index.\n\n### Selectors\n`get(state: {}): Ids[]`\nReturns the entire Index array.\n\n`includes(state: {}, { id: Id }): boolean`\nTakes id parameter and checks whether the identifier is present in the Index, returns boolean value.\n\n`count(state: {}) =\u003e number`\nReturns the number of Ids in the Index.\n\n## Custom Reducer Definitions\nCreate new reducer definitions with the `createDefinition` function. The resulting object is a valid definition that can be used.\n```js\nimport { createDefinition } from 'redux-definitions'\n\nconst SpecialField = createDefinition({\n  defaultState: 'morty',\n  reducers: {\n    set: (state, { payload }) =\u003e payload,\n    clear: () =\u003e undefined,\n  },\n  selectors: {\n    get: (state) =\u003e state\n  }\n})\n\nexport { SpecialField }\n```\n\n### Usage\n```js\nimport { createReducers } from 'redux-definitions'\nimport { SpecialField } from './specialField'\n\nconst { reducers } = createReducers({\n  people: {\n    rick: SpecialField\n  }\n})\n```\n\n# Roadmap\n1. Full TypeScript support\n   - typed action and selector maps\n   - typed action and selector payloads\n2. Support for adding additional user created actions to the REPL\n   - Helpful for making existing actions/reducers accessible by the REPL\n3. createSagas helper for creating actions that initiate sagas and add actions to REPL\n4. Research how to best add async saga support within definitions\n   - Most asynchronous business logic will involve multiple reducers and therefore should not be associated with a single reducer or definition. Business logic should use createSagas and live in its own user defined directory/file. That being said the library should support adding basic asynchronous code to definitions.\n\n# Appendix\n\n## Typescript\nRedux Definitions is written in TypeScript. Due to the generative nature of the library fully typed actions, reducers, and selectors have proven difficult to implement. The goal is to get to the point where all actions and reducers have fully typed payloads. ✨Contributions from anyone with ideas on how to achieve this are very appreciated! Feel free to open a Github [issue](https://github.com/redux-definitions/redux-definitions/issues/new) or start a conversation on [Spectrum](https://spectrum.chat/redux-definitions) with any thoughts or ideas.\n\n## Examples\n- [NextJS](https://github.com/redux-definitions/redux-definitions-nextjs-example)\n- NextJS Typescript  [Help with this](https://github.com/redux-definitions/redux-definitions/issues/17)\n- Create React App [Help with this](https://github.com/redux-definitions/redux-definitions/issues/16)\n- Create React App Typescript [Help with this](https://github.com/redux-definitions/redux-definitions/issues/18)\n\nPRs with other examples are appreciated!\n\n## FAQ\nComing soon!\n\n## Contributing\n\nPlease check out the [Contributing](https://github.com/redux-definitions/redux-definitions/blob/master/CONTRIBUTING.md) page to learn how to get involved.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredux-definitions%2Fredux-definitions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredux-definitions%2Fredux-definitions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredux-definitions%2Fredux-definitions/lists"}