{"id":19083959,"url":"https://github.com/floating/restore","last_synced_at":"2025-04-04T09:10:09.204Z","repository":{"id":42119103,"uuid":"94907031","full_name":"floating/restore","owner":"floating","description":"A predictable \u0026 observable state container for React apps","archived":false,"fork":false,"pushed_at":"2025-01-15T19:12:52.000Z","size":2873,"stargazers_count":141,"open_issues_count":28,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-28T08:07:31.579Z","etag":null,"topics":["devtools","immutability","observable","react","restore","simple"],"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/floating.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-20T15:31:52.000Z","updated_at":"2025-01-15T16:58:08.000Z","dependencies_parsed_at":"2024-06-21T15:45:42.351Z","dependency_job_id":"dc0b5a63-6c30-4365-ad76-75875a5f581e","html_url":"https://github.com/floating/restore","commit_stats":{"total_commits":47,"total_committers":4,"mean_commits":11.75,"dds":"0.17021276595744683","last_synced_commit":"21c1cbf158bbdaec13f3c236576bc85613f3f125"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floating%2Frestore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floating%2Frestore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floating%2Frestore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floating%2Frestore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floating","download_url":"https://codeload.github.com/floating/restore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247149505,"owners_count":20891954,"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":["devtools","immutability","observable","react","restore","simple"],"created_at":"2024-11-09T02:49:35.876Z","updated_at":"2025-04-04T09:10:09.182Z","avatar_url":"https://github.com/floating.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Restore\r\n\r\n*A predictable \u0026 observable state container for React apps*\r\n\r\n\r\n- __Simple__ - Reduced boilerplate, minimal interface, refined patterns, __~5kB__\r\n- __Observable__ - Subscriptions to value changes are automatic, eliminating unnecessary renders\r\n- __Predictable__ - Unidirectional data makes it easy to test, debug and reason about your application\r\n- __Immutable__ - Frozen state along with thaw/replace updates provide baked in immutability\r\n- __DevTools__ - Helpful tools, including time travel, provide clear visibility of your state, actions, updates \u0026 observers\r\n\r\n![Restore DevTools](http://i.imgur.com/xn7dtIs.gif)\r\n\r\n## Install\r\n\r\n```\r\nnpm install react-restore\r\n```\r\n\r\n## Creating a store\r\n\r\nA `store` holds the `state` of the application and the `actions` used to `update` that `state`\r\n\r\n```javascript\r\nimport Restore from 'react-restore'\r\nimport * as actions from './actions'\r\nlet initialState = {text: 'Hello World'}\r\nlet store = Restore.create(initialState, actions)\r\n```\r\n\r\n__Now we have a store!__\r\n\r\n## Accessing values in the store\r\n\r\nTo get the `text` value from the `store`\r\n```javascript\r\nstore('text') // 'Hello World'\r\n```\r\n## Updating values in the store\r\n\r\n* `actions` are used to make updates to the state of the `store`\r\n* `actions` are passed as an object during the `store`'s creation\r\n* The `actions` object can be created explicitly or by using import syntax\r\n  * e.g. `import * as actions from './actions'`\r\n* `actions` contain the whole lifecycle of an update making async updates easy to create and track\r\n\r\nLet's create an action called `setText` to `update` the `text` value in our `store`\r\n\r\n```javascript\r\nexport const setText = (update, newText) =\u003e {\r\n  update(state =\u003e {\r\n    state.text = newText\r\n    return state\r\n  })\r\n}\r\n```\r\n\r\n`setText` can now be called via the `store`\r\n\r\n```javascript\r\nstore.setText('Updated World')\r\n```\r\n\r\nThis would update the value `text` in the `store` to `'Updated World'`\r\n\r\n## The update method\r\n\r\n- `actions` are passed `update` as their first argument (followed by any arguments you passed to them)\r\n- The `update` method is how we replace values held by the `store`\r\n- The `update` method uses a pure updater function to perform these updates\r\n\r\nIf you look back at our `setText` `action` you can see our `updater` function\r\n\r\n```javascript\r\nstate =\u003e {\r\n  state.text = newText\r\n  return state\r\n}\r\n```\r\n\r\nThe `updater` function is passed the `state` (or more likely, part of the `state`) and returns an updated version of it\r\n\r\n## Targeting state updates\r\n\r\n- `update` takes a dot notation path as an optional first argument\r\n- This path allows you to target part of the `state` instead of the whole `state`\r\n- By doing this, only the components that care about what you're targeting will re-render and the rest will not\r\n\r\nFor example, our `setText` `action` could be\r\n\r\n```javascript\r\nexport const setText = (update, newText) =\u003e {\r\n update('text', text =\u003e {\r\n   return newText\r\n })\r\n}\r\n```\r\n\r\n__Targeting a more complex `state`__\r\n\r\n```javascript\r\nimport Restore from 'react-restore'\r\nimport * as actions from './actions'\r\nlet initialState = {\r\n  nested: {\r\n    wordOne: 'Hello',\r\n    wordTwo: 'World'\r\n  }\r\n}\r\nlet store = Restore.create(initialState, actions)\r\n```\r\n\r\nLet's create an `action` called `setNestedText` to `update` `wordTwo` in our `store`\r\n\r\n```javascript\r\nexport const setNestedText = (update, newValue) =\u003e {\r\n  update('nested.wordTwo', wordTwo =\u003e newValue)\r\n}\r\n```\r\n\r\nCalling it is the same as before\r\n\r\n```javascript\r\nstore.setNestedText('Updated World')\r\n```\r\n\r\nThis would `update` the value of `wordTwo` from `'World'` to `'Updated World'`\r\n\r\n__Multi-arg Paths__\r\n\r\nInstead of concatenating a string for the path passed to `store` or `update`, you can define your path with multiple arguments. For example if you had an id (`let id = 123`) for an item within the state you could break the path into multiple arguments, like so...\r\n\r\n```javascript\r\nlet name = store('items', id, 'name') // Gets the value of items[id].name from the store\r\n\r\n// When updating, the last argument is always the updater function\r\nupdate('items', id, 'name', name =\u003e 'bar') // Updates the value of items[id].name to 'bar'\r\n```\r\n\r\n## Connecting the store to your React components\r\n\r\nConnecting React components to the `store` is easy\r\n\r\n```javascript\r\nRestore.connect(Component)\r\n```\r\n\r\n- Once a component is connected, it will have access to the `store` via `this.store`\r\n- It will automatically re-render itself when a value it consumes from the `store` changes\r\n- A connected component inherits the `store` of its closest connected parent\r\n- At the top-level of your app you will explicitly connect a `store`, since it has no parent to inherit from\r\n- This top-level `store` will be passed down to your other connected components\r\n- We recommend using a single top-level `store` for your app\r\n\r\n```javascript\r\nRestore.connect(Component, store) // Explicitly connects store to Component\r\nRestore.connect(Component) // Component inherits store from closest parent Component\r\n```\r\n\r\nTo access the `store` from within a connected component, we do the same as before but this time referencing `this.store`\r\n```javascript\r\nthis.store('text')\r\n// or\r\nthis.store.setText('Updated World')\r\n```\r\n\r\n## Async Updates\r\n\r\nActions can contain synchronous and asynchronous updates, both are tracked and attributed to the action throughout its lifecycle. Here we'll make our `setText` action get the `newText` value from the server and then update the state asynchronously.\r\n\r\n``` javascript\r\nexport const setText = update =\u003e {\r\n  getTextFromServer(newText =\u003e {\r\n    update('text', text =\u003e newText)\r\n  })\r\n}\r\n```\r\n\r\nIt can be useful to compose synchronous and asynchronous updates together. Say you wanted to show a loading message while you fetched the `newText` value from the server. You could update a `loading` flag synchronously and then unset it later when you get the response.\r\n\r\n``` javascript\r\nexport const setText = update =\u003e {\r\n  update('loading', loading =\u003e true)\r\n  getTextFromServer(newText =\u003e {\r\n    update('loading', loading =\u003e false)\r\n    update('text', text =\u003e newText)\r\n  })\r\n}\r\n```\r\n\r\n## Enabling [DevTools](https://github.com/floating/restore-devtools)  / Time Travel\r\n\r\nRestore has a `\u003cDevTools /\u003e` component you can use to observe updates to the state and time travel through past actions\r\n```\r\nnpm install restore-devtools --save-dev\r\n```\r\n```javascript\r\nimport DevTools from 'restore-devtools'\r\n```\r\nDrop `\u003cDevTools /\u003e` anywhere in your application to enable the dev tools\r\n\r\nhttps://github.com/floating/restore-devtools\r\n\r\n## Standalone Observers\r\n\r\nConnected components are `observers` but you can use this functionality outside of components too!\r\n\r\n```javascript\r\nstore.observer(() =\u003e {\r\n  console.log(store('text'))\r\n})\r\n```\r\n\r\nThis function will run once immediately and again anytime the values it consumes change, in this case our `text` value\r\n\r\n## Putting it together\r\n\r\n`App.jsx`\r\n```jsx\r\n\r\nimport React from 'react'\r\nimport Restore from 'react-restore'\r\n\r\nclass App extends React.Component {\r\n  render () {\r\n    return (\r\n      \u003cdiv onClick={() =\u003e this.store.setText('Updated World')}\u003e\r\n        {this.store('text')}\r\n      \u003c/div\u003e\r\n    )\r\n  }\r\n}\r\n\r\nexport default Restore.connect(App)\r\n\r\n```\r\n\r\n`actions.js`\r\n```javascript\r\n\r\nexport const setText = (update, newText) =\u003e {\r\n  update('text', text =\u003e {\r\n    return newText\r\n  })\r\n}\r\n\r\n```\r\n\r\n`index.js`\r\n```javascript\r\n\r\nimport React from 'react'\r\nimport ReactDOM from 'react-dom'\r\nimport Restore from 'react-restore'\r\n\r\nimport App from './App.jsx'\r\nimport * as actions from './actions.js'\r\n\r\nlet initialState = {text: 'Hello World'}\r\nlet store = Restore.create(initialState, actions)\r\nlet Root = Restore.connect(App, store)\r\n\r\nReactDOM.render(\u003cRoot /\u003e, document.getElementById('root'))\r\n\r\n```\r\n\r\n## Projects using Restore\r\n  - [Frame](https://github.com/floating/frame) - A cross-platform Ethereum interface\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloating%2Frestore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloating%2Frestore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloating%2Frestore/lists"}