{"id":13499621,"url":"https://github.com/swipely/state-trooper","last_synced_at":"2025-03-29T05:31:55.473Z","repository":{"id":21382206,"uuid":"24699811","full_name":"swipely/state-trooper","owner":"swipely","description":"Centrally manage state for React applications with CSP","archived":false,"fork":false,"pushed_at":"2020-04-14T17:11:12.000Z","size":251,"stargazers_count":15,"open_issues_count":1,"forks_count":2,"subscribers_count":27,"default_branch":"master","last_synced_at":"2024-07-05T14:40:32.537Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://github.com/swipely/state-trooper","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/swipely.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}},"created_at":"2014-10-01T23:49:12.000Z","updated_at":"2022-05-14T20:11:58.000Z","dependencies_parsed_at":"2022-07-12T06:00:19.252Z","dependency_job_id":null,"html_url":"https://github.com/swipely/state-trooper","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swipely%2Fstate-trooper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swipely%2Fstate-trooper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swipely%2Fstate-trooper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swipely%2Fstate-trooper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swipely","download_url":"https://codeload.github.com/swipely/state-trooper/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213424283,"owners_count":15585280,"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":[],"created_at":"2024-07-31T22:00:36.780Z","updated_at":"2024-07-31T22:02:19.980Z","avatar_url":"https://github.com/swipely.png","language":"JavaScript","funding_links":[],"categories":["Uncategorized","Awesome React"],"sub_categories":["Uncategorized","Tools"],"readme":"State Trooper\n=============\n\n![](state-trooper-logo.png)\n\n![](https://travis-ci.org/swipely/state-trooper.svg?branch=master)\n\n# Install\n\nnpm\n```sh\nnpm install state-trooper\n```\n\nYarn\n```sh\nyarn add state-trooper\n```\n\n# Example Usage\n\nCall `StateTrooper.patrolRunLoop` in your route handler/main app entry point\n```javascript\n\nconst config = {\n  // describe the state for the page\n  state: {\n    serverReport: null,\n    bio: null,\n    activity: null\n  },\n\n  // describe the fetchers and persisters for each piece of state\n  // fetchers and persisters are functions that should return channels\n  dataStore: {\n    'serverReport': { fetcher: serverReportFetcher },\n    'bio': { fetcher: bioFetcher, persister: bioPersister },\n    'activity': { fetcher: activityFetcher }\n  }\n};\nconst cursor = StateTrooper.patrolRunLoop(config, (cursor) =\u003e {\n  // Re-render the component when state changes generate new cursors\n  React.render(\u003cServer cursor={cursor}/\u003e, document.querySelector('body'));\n});\n\n// Render the component with the initial cursor\nReact.render(\n  \u003cServer cursor={cursor}/\u003e,\n  document.querySelector('body')\n);\n\n```\n\nUsing cursors inside of the components\n```javascript\nconst Server = React.createClass({\n  render: function () {\n    return (\n      \u003cdiv\u003e\n        \u003cServerReport cursor={this.props.cursor.refine('serverReport')}/\u003e\n        \u003cBio cursor={this.props.cursor.refine('bio')}/\u003e\n      \u003c/div\u003e\n    );\n  }\n});\n\nconst Bio = React.createClass({\n  render: function () {\n    const bio = this.props.cursor.deref();\n\n    if (bio) {\n      return (\n        \u003cform\u003e\n          \u003clabel\u003eName\u003c/label\u003e\n          \u003cinput type='text' value={bio.name} onChange={this.handleChange}/\u003e\n          \u003cbutton onClick={this.handleSaveClick}\u003eSave\u003c/button\u003e\n        \u003c/form\u003e\n      );\n    }\n    else {\n      return null;\n    }\n  },\n\n  handleChange: function (ev) {\n    this.props.cursor.set({ name: ev.target.value });\n  },\n\n  handleSaveClick: function () {\n    this.props.cursor.persist();\n  }\n});\n```\n\nFetchers will be called with a `cursor` and a `rootCursor`\nPersisters will be called with a `cursor`, a `change` and a `rootCursor`\n\nTo have your fetchers or persisters change the state, simply use one of the\nmutating functions of a cursor.\n\n# How state changes are applied\nState change are applied one after each other. Making a change will always\nproduce a new top level cursor with the update state.  However there are cases\nwhere you can call request several mutate changes via any of the mutative\nfunctions on a cursor (`set`, `replace`, `add`, `remove`) in short succession.\nIn cases like this the state changes might be requested before the first change\nis propegated. StateTrooper ensures that the changes are applied sequentially\nregardless of wether or not a new cursor has been added to the cursor chan yet.\n\n# Cursor API:\nGiven this state:\n```javascript\n{\n  foo: {\n    bar: 'baz',\n    beep: ['hey', 'yo']\n  }\n}\n```\n\n### cursor#refine\n```javascript\ncursor.refine('foo.bar');\n```\nCalling cursor#refine with a string path will create a new cursor for that part\nof the state.  This is useful as you go down the component tree and the focus\nyour component and state on a specific domain.\n\n### cursor#deref\n```javascript\ncursor.refine('foo').deref();\n// or\ncursor.deref('foo');\n```\nCalling cursor#deref returns the value at the refined location.\nIf a path is provided, the path will be used starting at the refined location.\n\n### cursor#set\n```javascript\ncursor.refine('foo').set({ bar: 'foo' });\n```\nCalling cursor#set will merge the changes into the state at the path of the\ncursor. Similar to reacts setState.\nIn this example the state change will result in:\n```javascript\n{\n  foo: {\n    bar: 'foo',\n    beep: ['hey', 'yo']\n  }\n}\n```\nset is only available on Objects/Maps\n\n### cursor#replace\n```javascript\ncursor.refine('foo').replace('bar');\n```\nCalling cursor#replace will replace the entire subtree at the path of the cursor\nIn this example the state change will result in:\n```javascript\n{\n  foo: 'bar'\n}\n```\n\n### cursor#remove\n```javascript\ncursor.refine('foo.beep.0').remove();\n```\nCalling cursor#remove will remove the entire subtree at the path of the cursor.\nThis works on both arrays and objects.\nIn this example the state change will result in:\n```javascript\n{\n  foo: {\n    bar: 'baz',\n    beep: ['yo']\n  }\n}\n```\n\n### cursor#add\n```javascript\ncursor.refine('foo.beep').add('yo');\n```\nCalling cursor#add will push a new value on the end of the array held by the cursor.\nIn this example the state change will result in:\n```javascript\n{\n  foo: {\n    bar: 'baz',\n    beep: ['hey', 'yo', 'yo']\n  }\n}\n```\nadd is only available on `Array` values.\n\n### cursor#equals\n```javascript\nthis.state.cursor.equals(newState.cursor);\n```\nThis method compares the state encapsulated by two cursors. If the cursors hold equivalent values,\nbased on the result of an `equals()` method, comparison by `Object.prototype.valueOf()`,\nor comparison of key/value pairs on the object.\nIf you are a `React` user, this method can be useful for implementing `shouldComponentUpdate`.\n\n# Monitoring Changes\n\nStateTrooper also provides a mechanism for monitoring a piece of state and being notified when it changes.\nThese are called \"stakeouts\". These are useful when an application needs to respond to a change in one piece of state,\nsuch as changing a user preference, and make further state changes.\n\n### StateTrooper.stakeout\n```javascript\nStateTrooper.stakeout('activityReport', (update, cursor) =\u003e {\n  // The `update` holds the new value and the action that caused the update\n  // The `cursor` is the root cursor.\n  if (update.value.somethingExcitingChanged) {\n    fetch(myExcitingService)\n      .then(response =\u003e response.json())\n      .then(json =\u003e cursor.replace('myExcitingState', json));\n  }\n});\n```\n\n### StateTrooper.patrol\n```javascript\n  function handleActivityReportChange(update, cursor) {\n    console.log('handleActivityReportChange', update);\n  }\n\n  const cursorChan = StateTrooper.patrol({\n    // describe the state for the page\n    state: {\n      activity: null,\n      activityReport: null\n    },\n    stakeout: {\n      'activityReport': [handleActivityReportChange]\n    }\n  });\n\n```\n\n# API Changes\n\nA major change in the 2.x release of state-trooper is the removal of `immutable-js` as the internal data structure for storing state.\n\nThe following APIs changed in v2.x:\n\n- cursor#deref(): Values returned are not `immutable-js` Maps or Lists.\n- cursor#derefJS(): *REMOVED*\n- cursor#hasSameValue(): Renamed to `cursor#equals`.\n- cursor#map(): *REMOVED*\n\n- StateTrooper#stakeout(): *ADDED*\n- StateTrooper#patrol(): The \"config\" object supports a `stakeout` property.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswipely%2Fstate-trooper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswipely%2Fstate-trooper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswipely%2Fstate-trooper/lists"}