{"id":19451331,"url":"https://github.com/thekashey/restate","last_synced_at":"2025-04-25T04:30:25.239Z","repository":{"id":57343208,"uuid":"115996551","full_name":"theKashey/restate","owner":"theKashey","description":"A redux fractal state library 🕷 ","archived":false,"fork":false,"pushed_at":"2020-05-29T04:23:33.000Z","size":7839,"stargazers_count":54,"open_issues_count":27,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-18T14:39:06.932Z","etag":null,"topics":["composition","fractal","react","redux","state","state-management"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/theKashey.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":"2018-01-02T09:11:21.000Z","updated_at":"2024-02-13T15:01:09.000Z","dependencies_parsed_at":"2022-09-16T07:51:26.127Z","dependency_job_id":null,"html_url":"https://github.com/theKashey/restate","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Frestate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Frestate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Frestate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Frestate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theKashey","download_url":"https://codeload.github.com/theKashey/restate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250754502,"owners_count":21481823,"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":["composition","fractal","react","redux","state","state-management"],"created_at":"2024-11-10T16:41:17.623Z","updated_at":"2025-04-25T04:30:24.594Z","avatar_url":"https://github.com/theKashey.png","language":"JavaScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eRESTATE\u003c/h1\u003e\n  \u003cbr/\u003e\n  \u003cimg src=\"https://cdn.rawgit.com/theKashey/restate/1d67d86d/images/logo.svg\" alt=\"restate\" width=\"600\" align=\"center\"\u003e\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  A fractal \u003cu\u003estate management\u003c/u\u003e library. \n  \u003cbr/\u003e\n  \u003cbr/\u003e\n  \u003ca href=\"https://circleci.com/gh/theKashey/restate/tree/master\"\u003e\n   \u003cimg src=\"https://img.shields.io/circleci/project/github/theKashey/restate/master.svg?style=flat-square)\" alt=\"Build status\"\u003e\n  \u003c/a\u003e \u003ca href='https://greenkeeper.io/'\u003e\u003cimg src=\"https://badges.greenkeeper.io/theKashey/restate.svg\" alt=\"Greenkeeper\" /\u003e\u003c/a\u003e\n  \u003cbr/\u003e\n\u003c/div\u003e\n\nRestate connects to the Redux State __AND__ to the local component state, producing a new state, you can use with, or without Redux.\n\n\u003e Restate, re-store, redux-focus, redux-lenses, re-dux, redux-tree... Oh, it was not easy to name _The base layer for a redux composition_.\n\nThe goal of Restate is to provide hierarchical, decoupled, isolated synthetic stores, and make your application faster and simpler.\nRestate just creates a new branch, from a original store, allowing you to control it, and use **composition\non redux**-level. And it does not need Redux for it. You can use Restate without Redux, _connecting_ your components to the syntetic derived state.\n\n```js\nimport reduxRestate from 'redux-restate'; // to low-level redux manupulations\nimport reactReduxRestate from 'react-redux-restate'; // to work with multiple stores\nimport reactReduxFocus from 'react-redux-focus'; // to focus a lens on a single store\nimport reactReduxLoop from 'react-redux-loop'; // to call React from Redux\nimport reactSemaphore from 'react-redux-semaphore'; // to create *suspense* \n```\n\n## The problem\n\nAs long React spreads component architecture and highly composable patterns,\nthe major part of it - Redux - do not follow this way.\n\nIn the world of redux **Store is a singlentone**.\nYou can create a connection to that store, and fetch the data you need. But how?\n\n`Redux is the same for any connection, created from any point of Render Tree.`\n\nYou have to use React props, to pass the `ids` you need deeply into react Tree to use them to get the data out of the store.\n\nRedux is not composable. Redux is not component friendly.\n\nMedium articles about:\n [Restate](https://medium.com/@antonkorzunov/restate-the-story-of-redux-tree-27d8c5d1040a)\n , [Fractal state](https://blog.cloudboost.io/the-state-of-the-state-of-the-state-f93c8bdc6b1b)\n .\n \n\n## The borders\n\nRedux's connect method produces PureComponent.\nNo update from the top will pass PureComponent.\nAll updates will start from PureComponent.\n\n`Connect is the end for all updates, and the beginning.`\n\nThen you will change the store, **all** connected component will be triggered to update.\nThey will mapStateToProps and **maybe** do nothing more, in case the result object is shallowEqual to the older one.\n\nUnless you will specify `areStatesEqual` for each connect, which is not quite possible, to say the truth.\n\n## The idea\n\nThis is rework of ideas from Yandex Map API [Option Manager](https://tech.yandex.com/maps/doc/jsapi/2.1/ref/reference/option.Manager-docpage/).\nOption Manager was build to handle 2 cases:\n\n* GeoJSON. Where a lot of similar objects can be nested inside each other (features and collections).\n* The single configuration file.\n\nIt is easier to explain via example:\n\n1. You are setTimeone. You need your durition, and you are reading from store values named -\u003e `duration`\n2. Actually you are an animation. You prepend your request by your name -\u003e `animationDuration`\n3. Animation is internal component of Zoom Control. And it add to all data-requests passed from nested components it's name -\u003e `zoomControlAnimationDuration`.\n   As result - final component could use simple names - `color`, `duration`, `value`,- but store could contains much more complex names.\n\nOptionManager (OptionMapper to be clear) work as lenses _scoping the store_.\n\n## The solution\n\nRedux-restate get:\n\n* one or more stores as input,\n* combine function to create a new store\n* dispatch function, to handle dispatch\n\nAnd produces the new store.\n\nThus makes redux composable, and enabled the component way.\n\n\u003e Restate is the end for any update, and the beginning. But not for all. Only the ones you need.\n\n## The implementation\n\n* redux-restate for redux level.\n* react-redux-retate for react multy-state case.\n* react-redux-focus for single store case.\n* recct-redux-semaphore to control update propagation.\n\n# Usage\n\n## redux-restate\n\n```js\nimport restate from 'redux-restate';\nconst newStore = restate({ store: baseStore }, composeState, routeDispatch, options);\n```\n\n* `composeState(states):NewState` get one one more `states` as input, produce the output\n* `routeDispatch(dispatchers, event)` get one one more dispatch as input, then call the disired one with even, also provided.\n\n### react-redux-restate\n\n```js\nimport reactReduxRestate from 'react-redux-restate';\nconst RestatedComponent = reactReduxRestate(\n  { otherStore: otherStore /*store or store key*/ }, // default store will be injected automagically\n  (stores, props) =\u003e composeState,\n  (dispatchers, event, props) =\u003e routeDispatch,\n  options,\n)(WrappedComponent);\n```\n\nThe same as redux-restate, but in form of React HOC.\nThe default store, accessible with storeKey, is available as .default for next functions.\n\n* `composeState(states, event, props)` get one one more `states` as input, plus props, produce the output\n\n* `routeDispatch(dispatchers, event, props)` dispatch as input, plus props, then call the disired one with even, also provided.\n\n\u003e Note: if composeState will return _undefined_ the state will not change.\n\n### reproviding a state\n\nSometimes it is worth to keep the old store. Just `save` it using a different name.\n\n```js\nimport { reprovide } from 'react-redux-restate';\nconst Reprovider = reprovide('new-store-name', 'old-store-name');\nconst DefaultReprovider = reprovide('new-store-name'); // old will be `store`\n```\n\n### Fork/Unfork\n\nThere is a \"standard way\" to reprovide a state\n\n```js\nimport { ForkReduxStore, UnforkReduxStore } from 'react-redux-restate';\n\nconst App = () =\u003e (\n  \u003cProvider state={state}\u003e\n    \u003cForkReduxState\u003e\n      // state \"forked\" into \"global\" state\n      \u003cFocusOrRestateTheStore\u003e\n        // state is \"altered\" here // you can always refer to \"global\" as a state key\n        \u003cUnforkReduxState\u003e// state is reverted to the original\u003c/UnforkReduxState\u003e\n      \u003c/FocusOrRestateTheStore\u003e\n    \u003c/ForkReduxState\u003e\n  \u003c/Provider\u003e\n);\n```\n\n### react-redux-focus\n\n```js\nimport reactReduxFocus from 'react-redux-focus';\nconst FocusedComponent = reactReduxFocus(\n  (state, props) =\u003e state.todos[props.id],\n  (dispatch, event, props) =\u003e dispatch({ ...event, id: props.id }),\n)(WrappedComponent);\n```\n\nOr you can use Component approach\n\n```js\nimport { ReduxFocus } from 'react-redux-focus';\n\n\u003cReduxFocus\n  focus={(state, props) =\u003e state.todos[props.id]}\n  onDispatch={(dispatch, event, props) =\u003e dispatch({ ...event, id: props.id })}\n\u003e\n  \u003cWrappedComponent /\u003e\n\u003c/ReduxFocus\u003e;\n```\n\nThe same as react-redux-restate, but for a single store.\n\n* `composeState(state, props): newState` - focus will work only with one state\n* `routeDispatch(dispatch, props)`\n\n### react-redux-semaphore\n\nHOC approach.\n\n```js\nimport reduxSemaphore from 'react-redux-semaphore';\n\nconst WillUseOldStateUnlessConditionAreMet = reduxSemaphore((state, props) =\u003e isValid(store.importantData))(\n  TargetComponent,\n);\n```\n\nComponent approach\n\n```js\nimport { ReduxSemaphore } from 'react-redux-semaphore';\n\u003cReduxSemaphore condition={(state, props) =\u003e isValid(store.importantData)}\u003e\n  \u003cTargetComponent /\u003e\n\u003c/ReduxSemaphore\u003e;\n```\n\n## Optimization\n\nRestate will perform shallowEqual compare for the old and the new states.\nPlease use `reselect` or another memoization library to keep branches unchanged.\nOtherwise - specify `areStatesEqual` option.\n\n## Multiple store case\n\nIt is absolutely common, that some parts of application can be **absolutely** independent.\nBut, in the same time, they are united. The simplest examples\n\n* routing\n* notifications\n* user data\n* analytics\n\nUsing the `composeState` you can control how your data passes **down**.  \nUsing the `routeDispatch` you can control how dispatches bubbles **up**.\n\n* You can extend dispatched event by some data.\n* You can choose which dispatch to call - the original one, from a nearest (default) store, or the parent one (the Application level)\n\n## Examples\n\n#### Deepdive with Restart instantly\n\nConnect all `restate` to the original store, just for original store lensing and optimization.\n\n```js\n import {createProvider} from 'react-redux'\n import reactReduxRestate from 'react-redux-restate';\n\n const Provider = createProvider('non-default-name');\n const Remap = reactReduxRestate(...., {\n   storeKey:'non-default-name'\n   // restoreKey: 'store' // will defaults to store\n })\n```\n\n#### Isolate middle of application\n\nProvide store, reprovide store, restore original store...\n\n```js\nimport reactReduxRestate, { reprovider } from 'react-redux-restate';\nconst Reprovider = reprovider('realStore');\nconst RestoreStore = reprovider('store', 'realStore');\n\nconst RestateStore = reactReduxRestate(\n  {}, // no extra stores\n  states =\u003e focusOnSomeBranchIn(states.default),\n  (dispatch, event, props) =\u003e dispatch.default({ ...event, id: props.id }),\n);\n\nconst RestateWithOriginalStore = reactReduxRestate(\n  { realStore: 'realStore' }, // connect the real store back\n  states =\u003e mixStates(states.default, states.realStore),\n  (dispatch, event, props) =\u003e dispatch.realStore({ ...event, id: props.id }),\n);\n\nconst RestateForOriginalStore = reactReduxRestate(\n  {}, // no extra stores\n  states =\u003e focusOnSomeBranchIn(states.default),\n  (dispatch, event, props) =\u003e dispatch.default({ ...event, id: props.id }),\n  {\n    storeKey: 'realStore', // use `realStore` as default\n  },\n);\n\nconst Application = () =\u003e (\n  // put redux store inside\n  \u003cProvider store={myReduxStore}\u003e\n    // re-export current store as `realStore\n    \u003cReprovider\u003e\n      // switch to syntetic redux\n      \u003cRestateStore\u003e\n        \u003cSomePartOfAnApplication\u003e\n          // restore original store\n          \u003cRestoreStore\u003e\n            \u003cWorkWithOriginalStore /\u003e\n          \u003c/RestoreStore\u003e\n          // or use restate with 2 stores connected\n          \u003cRestateWithOriginalStore /\u003e\n          // or connect restate to the real store\n          \u003cRestateForOriginalStore /\u003e\n        \u003c/SomePartOfAnApplication\u003e\n      \u003c/RestateStore\u003e\n    \u003c/Reprovider\u003e\n  \u003c/Provider\u003e\n);\n```\n\nAlso Check out example-todo in packages.\n\n### Before all the things - map Todos\n\n```js\nconst mapStateToProps = state =\u003e ({\n  todos: getVisibleTodos(state.todos, state.visibilityFilter),\n});\n\nconst mapDispatchToProps = {\n  onTodoClick: toggleTodo,\n};\n\nexport const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);\n```\n\nNext - render Todo...\n\n### The original Redux-todo-list\n\n```js\nconst TodoListRedux = ({ todos, onTodoClick }) =\u003e (\n  \u003cul\u003e\n    {todos.map(todo =\u003e (\n      // Here redux \"ends\". You have to map onClick in magic way\n      \u003cTodo key={todo.id} {...todo} onClick={() =\u003e onTodoClick(todo.id)} /\u003e\n    ))}\n  \u003c/ul\u003e\n);\n```\n\n### The remap variant\n\n```js\n// direct mapping. Here is nothing more that Todo need\nconst mapStateToProps = state =\u003e state;\nconst mapDispatchToProps = {\n  onClick: toggleTodo,\n};\nconst ConnectedTodo = connect(mapStateToProps, mapDispatchToProps)(Todo);\n\n// Focusing on Todo-only\nconst TodoMapped = reduxFocus(\n  (state, props) =\u003e state.todos[props.id],\n  // Todo should just dispatch an event. All logic is here.\n  (dispatch, event, props) =\u003e dispatch({ ...event, id: props.id }),\n)(ConnectedTodo);\n\nconst TodoList = ({ todos, onTodoClick }) =\u003e \u003cul\u003e{todos.map(todo =\u003e \u003cTodoMapped key={todo.id} id={todo.id} /\u003e)}\u003c/ul\u003e;\n```\n\nThe variant with `Remap` is twice longer, but it will run faster out of the box.\nNo Todo will be re-rendered if any other gonna to change\nTodo will become `isolated` from rest of application.\n\n[![Animation](images/restate-todo.gif?raw=true \"Todolist\")]\n\nAs result __you can re-connect any existing \"connected\" component__.\n\n## Licence\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Frestate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthekashey%2Frestate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthekashey%2Frestate/lists"}