{"id":31809789,"url":"https://github.com/shakacode/redux-interactions","last_synced_at":"2025-10-11T05:50:52.928Z","repository":{"id":55844901,"uuid":"70414553","full_name":"shakacode/redux-interactions","owner":"shakacode","description":"Composing UI as a set of interactions","archived":false,"fork":false,"pushed_at":"2020-12-11T10:13:23.000Z","size":379,"stargazers_count":21,"open_issues_count":7,"forks_count":1,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-05-01T09:45:49.213Z","etag":null,"topics":["react","reducer","redux","redux-apps","redux-interactions","state-management"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shakacode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-09T16:07:21.000Z","updated_at":"2022-11-21T05:59:15.000Z","dependencies_parsed_at":"2022-08-15T07:50:35.004Z","dependency_job_id":null,"html_url":"https://github.com/shakacode/redux-interactions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/shakacode/redux-interactions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakacode%2Fredux-interactions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakacode%2Fredux-interactions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakacode%2Fredux-interactions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakacode%2Fredux-interactions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shakacode","download_url":"https://codeload.github.com/shakacode/redux-interactions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shakacode%2Fredux-interactions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279006357,"owners_count":26084088,"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-10-11T02:00:06.511Z","response_time":55,"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":["react","reducer","redux","redux-apps","redux-interactions","state-management"],"created_at":"2025-10-11T05:50:40.240Z","updated_at":"2025-10-11T05:50:52.918Z","avatar_url":"https://github.com/shakacode.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# redux-interactions\n\nThinking of UI in terms of interactions makes life easier.\n\n\n## What and Why\n\n\u003e This is not a library, but a pattern.\n\nThere are two ways of thinking of about UI: in terms of **state** or in terms of **interactions**.\n\n### State-based model\nThis pattern is common in the Redux community. In this style of thinking, the building blocks of the app are **reducers**. Each reducer is tightly coupled to a specific part of the state. It decides how to respond to actions from the outside. It has full control over its part of the state, and that’s its only concern.\n\nThe main take away here is that **state is smart**.\n\n\n### Interactions-based model\nIn this model, application state is represented as an **inert tree**.\n\n\u003e **Check out [`redux-tree`](https://github.com/shakacode/redux-tree) as it's a big part of interactions pattern.**\n\nWhen a user interacts with the UI, the application changes its state in response. As opposed to a reducers-based model, state is a passive data container here. And **interactions** are the ones in charge.\n\n```\nstate:\n  entities:\n    posts: { index, entities }\n    comments: { entities }\n  ui:\n    postsList: { processingPosts }\n    ...\n```\n_State tree_\n\nLet’s say a user manages his posts and removes one of them by clicking the “Delete” button. What’s happening under the hood? The state of this UI part is stored in the `state.ui.postsList` leaf. Clicking on the button, a user triggers an action creator and the app starts a request to the server. In response to this action, `postId` is added to the `processingPosts` set to show the spinner in the UI. It requires a change of the single `ui.postsList` leaf. Let’s describe it in the interaction module:\n\n```js\n// Action creator: returns request action\nconst requestAction = postId =\u003e ({\n  type: 'POST_DELETE_REQUESTED',\n  postId,\n});\n\n// Action handler: reduces the state of the single leaf\nconst onRequest = {\n  POST_DELETE_REQUESTED:\n    (state, { postId }) =\u003e\n      state.update('processingPosts', postIds =\u003e postIds.add(postId)),\n};\n```\n\nWhen a server responds with a success:\n* `postId` must be removed from the `processingPosts`\n* `post` entity must be removed from the `entities.posts` leaf. \n\nThis action entails changing 2 different leaves:\n\n```js\n// Action creator: returns success action\nconst successAction = postId =\u003e ({\n  type: 'POST_DELETE_SUCCEEDED',\n  postId,\n});\n\n// Action handlers: passing array of the reducers for this action type\n//                  to apply sequence of the changes to the state tree\nconst onSuccess = {\n  POST_DELETE_SUCCEEDED: [\n    // 1. hide spinner\n    (state, { postId }) =\u003e\n      state.update('processingPosts', postIds =\u003e postIds.delete(postId)),\n\n    // 2. remove post entity\n    {\n      leaf: ['entities', 'posts'], // \u003c= keypath to the leaf of the state\n      reduce:\n        (postsEntitiesState, { postId }) =\u003e\n          postsEntitiesState\n            .updateIn(['index'], index =\u003e index.delete(postId))\n            .updateIn(['entities'], entities =\u003e entities.delete(postId)),\n    },\n  ],\n};\n```\n\nNotice how easy it is to follow what’s going on here because the logic of a single interaction is contained entirely in a single module. Try it and you will see how easy it is writing code like this.\n\nThe key point is that an interaction decides which part(s) of the state will be updated in response to the action.\n\n\n## Files structure\nThis approach proposes to represent **interactions as a modules**.\n\n```\n|- components/            # representation\n  |-- index.js\n  |-- index.css\n|- interactions/          # modules w/ interactions\n  |-- postEdit.js\n  |-- postDelete.js\n|- selectors/             # data selectors\n  |-- ...\n|- index.js               # container w/ `connect`\n|- actions.js             # list of action creators\n|- leaf.js                # leaf creator (see redux-tree)\n|- state.js               # state definition\n```\n\nEverything about the interaction is declared in the `interaction` module. Once interaction is written, connect it to the store using [`redux-tree`](https://github.com/shakacode/redux-tree) and you're all set.\n\nMain wins here:\n\n* **Changing things is easy**\u003cbr\u003e\nAll the changes in the app caused by the interaction are gathered in one place that’s easy to find, easy to reason about, and easy to change, move or remove. In each case, you’re dealing with files and folders dedicated to a given interaction instead of chunks of code scattered around disparate actions and reducer modules.\n* **Better focus**\u003cbr\u003e\nWhen you’re working on an interaction, you’re focused only on code related to an interaction. There aren’t any distractions from unrelated code.\n* **Stronger typings**\u003cbr\u003e\nWith `redux-tree` you get 1-to-1 mapping between action creator and action handler. It means you can strictly type an action that you dispatch via AC and this is exactly what you receive in AH. See [flow example](./examples/flow-interactions).\n\n\n## Examples\n* **basic-interactions** [ [live](http://redux-basic-interactions.surge.sh) \u0026middot; [source](./examples/basic-interactions) ]\u003cbr\u003e\nSimple counter reimplemented with `interactions`.\n* **async-interactions** [ [live](http://redux-async-interactions.surge.sh) \u0026middot; [source](./examples/async-interactions) ]\u003cbr\u003e\nThe real world app example.\n* **flow-interactions** [ [live](http://redux-flow-interactions.surge.sh) \u0026middot; [source](./examples/flow-interactions) ]\u003cbr\u003e\nRedux app typed w/ `flow`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshakacode%2Fredux-interactions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshakacode%2Fredux-interactions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshakacode%2Fredux-interactions/lists"}