{"id":13394228,"url":"https://github.com/jamiebuilds/unstated","last_synced_at":"2025-05-14T05:10:58.423Z","repository":{"id":28909587,"uuid":"119693982","full_name":"jamiebuilds/unstated","owner":"jamiebuilds","description":"State so simple, it goes without saying","archived":false,"fork":false,"pushed_at":"2023-09-11T02:47:52.000Z","size":448,"stargazers_count":7778,"open_issues_count":18,"forks_count":268,"subscribers_count":88,"default_branch":"master","last_synced_at":"2025-04-27T14:51:29.979Z","etag":null,"topics":["management","react","state"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":false,"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/jamiebuilds.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}},"created_at":"2018-01-31T13:56:02.000Z","updated_at":"2025-04-25T02:39:00.000Z","dependencies_parsed_at":"2022-08-07T14:00:57.156Z","dependency_job_id":"afc6d7e0-7f98-4ef9-93c0-57ed56cf2b84","html_url":"https://github.com/jamiebuilds/unstated","commit_stats":{"total_commits":89,"total_committers":27,"mean_commits":"3.2962962962962963","dds":0.449438202247191,"last_synced_commit":"0c8b3102d2ed404672a641d768f888804a2e7d2e"},"previous_names":["thejameskyle/unstated"],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamiebuilds","download_url":"https://codeload.github.com/jamiebuilds/unstated/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076850,"owners_count":22010611,"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":["management","react","state"],"created_at":"2024-07-30T17:01:13.213Z","updated_at":"2025-05-14T05:10:58.400Z","avatar_url":"https://github.com/jamiebuilds.png","language":"JavaScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/thejameskyle/unstated/master/logo.png\" alt=\"Unstated Logo\" width=\"400\"\u003e\n  \u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\u003c/div\u003e\n\n# Unstated\n\n\u003e State so simple, it goes without saying\n\n### :wave: [Check out the *-next version of Unstated with an all new React Hooks API \u0026rarr;](https://github.com/jamiebuilds/unstated-next)\n\n## Installation\n\n```sh\nyarn add unstated\n```\n\n## Example\n\n```jsx\n// @flow\nimport React from 'react';\nimport { render } from 'react-dom';\nimport { Provider, Subscribe, Container } from 'unstated';\n\ntype CounterState = {\n  count: number\n};\n\nclass CounterContainer extends Container\u003cCounterState\u003e {\n  state = {\n    count: 0\n  };\n\n  increment() {\n    this.setState({ count: this.state.count + 1 });\n  }\n\n  decrement() {\n    this.setState({ count: this.state.count - 1 });\n  }\n}\n\nfunction Counter() {\n  return (\n    \u003cSubscribe to={[CounterContainer]}\u003e\n      {counter =\u003e (\n        \u003cdiv\u003e\n          \u003cbutton onClick={() =\u003e counter.decrement()}\u003e-\u003c/button\u003e\n          \u003cspan\u003e{counter.state.count}\u003c/span\u003e\n          \u003cbutton onClick={() =\u003e counter.increment()}\u003e+\u003c/button\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/Subscribe\u003e\n  );\n}\n\nrender(\n  \u003cProvider\u003e\n    \u003cCounter /\u003e\n  \u003c/Provider\u003e,\n  document.getElementById('root')\n);\n```\n\nFor more examples, see the `example/` directory.\n\n## Happy Customers\n\n\u003ch4 align=\"center\"\u003e\n  \"Unstated is a breath of fresh air for state management. I rewrote my whole app to use it yesterday.\"\n  \u003cbr\u003e\u003cbr\u003e\n  \u003ca href=\"https://twitter.com/sindresorhus\"\u003eSindre Sorhus\u003c/a\u003e\n\u003c/h4\u003e\n\n\u003ch4 align=\"center\"\u003e\n  \"When people say you don't need Redux most of the time, they actually mean you do need Unstated.\u003cbr\u003eIt's like setState on fucking horse steroids\"\n  \u003cbr\u003e\u003cbr\u003e\n  \u003ca href=\"https://twitter.com/ken_wheeler\"\u003eKen Wheeler\u003c/a\u003e (obviously)\n\u003c/h4\u003e\n\n## Guide\n\nIf you're like me, you're sick of all the ceremony around state management in\nReact, you want something that fits in well with the React way of thinking,\nbut doesn't command some crazy architecture and methodology.\n\nSo first off: Component state is nice! It makes sense and people can pick it\nup quickly:\n\n```jsx\nclass Counter extends React.Component {\n  state = { count: 0 };\n  increment = () =\u003e {\n    this.setState({ count: this.state.count + 1 });\n  };\n  decrement = () =\u003e {\n    this.setState({ count: this.state.count - 1 });\n  };\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cspan\u003e{this.state.count}\u003c/span\u003e\n        \u003cbutton onClick={this.decrement}\u003e-\u003c/button\u003e\n        \u003cbutton onClick={this.increment}\u003e+\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n```\n\nAs a new React developer you might not know exactly how everything works, but\nyou can get a general sense pretty quickly.\n\nThe only problem here is that we can't easily share this state with other\ncomponents in our tree. Which is intentional! React components are designed to\nbe very self-contained.\n\nWhat would be great is if we could replicate the nice parts of React's\ncomponent state API while sharing it across multiple components.\n\nBut how do we share values between components in React? Through \"context\".\n\n\u003e **Note:** The following is part of the new `React.createContext` API\n\u003e [described in this RFC](https://github.com/reactjs/rfcs/blob/master/text/0002-new-version-of-context.md).\n\n```jsx\nconst Amount = React.createContext(1);\n\nclass Counter extends React.Component {\n  state = { count: 0 };\n  increment = amount =\u003e { this.setState({ count: this.state.count + amount }); };\n  decrement = amount =\u003e { this.setState({ count: this.state.count - amount }); };\n  render() {\n    return (\n      \u003cAmount.Consumer\u003e\n        {amount =\u003e (\n          \u003cdiv\u003e\n            \u003cspan\u003e{this.state.count}\u003c/span\u003e\n            \u003cbutton onClick={() =\u003e this.decrement(amount)}\u003e-\u003c/button\u003e\n            \u003cbutton onClick={() =\u003e this.increment(amount)}\u003e+\u003c/button\u003e\n          \u003c/div\u003e\n        )}\n      \u003c/Amount.Consumer\u003e\n    );\n  }\n}\n\nclass AmountAdjuster extends React.Component {\n  state = { amount: 0 };\n  handleChange = event =\u003e {\n    this.setState({\n      amount: parseInt(event.currentTarget.value, 10)\n    });\n  };\n  render() {\n    return (\n      \u003cAmount.Provider value={this.state.amount}\u003e\n        \u003cdiv\u003e\n          {this.props.children}\n          \u003cinput type=\"number\" value={this.state.amount} onChange={this.handleChange}/\u003e\n        \u003c/div\u003e\n      \u003c/Amount.Provider\u003e\n    );\n  }\n}\n\nrender(\n  \u003cAmountAdjuster\u003e\n    \u003cCounter/\u003e\n  \u003c/AmountAdjuster\u003e\n);\n```\n\nThis is already pretty great. Once you get a little bit used to React's way of\nthinking, it makes total sense and it's very predictable.\n\nBut can we build on this pattern to make something even nicer?\n\n### Introducing Unstated\n\nWell this is where Unstated comes in.\n\nUnstated is designed to build on top of the patterns already set out by React\ncomponents and context.\n\nIt has three pieces:\n\n##### `Container`\n\nWe're going to want another place to store our state and some of the logic for\nupdating it.\n\n`Container` is a very simple class which is meant to look just like\n`React.Component` but with only the state-related bits: `this.state` and\n`this.setState`.\n\n```js\nclass CounterContainer extends Container {\n  state = { count: 0 };\n  increment = () =\u003e {\n    this.setState({ count: this.state.count + 1 });\n  };\n  decrement = () =\u003e {\n    this.setState({ count: this.state.count - 1 });\n  };\n}\n```\n\nBehind the scenes our `Container`s are also event emitters that our app can\nsubscribe to for updates. When you call `setState` it triggers components to\nre-render, be careful not to mutate `this.state` directly or your components\nwon't re-render.\n\n###### `setState()`\n\n`setState()` in `Container` mimics React's `setState()` method as closely as\npossible.\n\n```js\nclass CounterContainer extends Container {\n  state = { count: 0 };\n  increment = () =\u003e {\n    this.setState(\n      state =\u003e {\n        return { count: state.count + 1 };\n      },\n      () =\u003e {\n        console.log('Updated!');\n      }\n    );\n  };\n}\n```\n\nIt's also run asynchronously, so you need to follow the same rules as React.\n\n**Don't read state immediately after setting it**\n\n```js\nclass CounterContainer extends Container {\n  state = { count: 0 };\n  increment = () =\u003e {\n    this.setState({ count: 1 });\n    console.log(this.state.count); // 0\n  };\n}\n```\n\n**If you are using previous state to calculate the next state, use the function form**\n\n```js\nclass CounterContainer extends Container {\n  state = { count: 0 };\n  increment = () =\u003e {\n    this.setState(state =\u003e {\n      return { count: state.count + 1 };\n    });\n  };\n}\n```\n\nHowever, unlike React's `setState()` Unstated's `setState()` returns a promise,\nso you can `await` it like this:\n\n```js\nclass CounterContainer extends Container {\n  state = { count: 0 };\n  increment = async () =\u003e {\n    await this.setState({ count: 1 });\n    console.log(this.state.count); // 1\n  };\n}\n```\n\nAsync functions are now available in [all the major browsers](https://caniuse.com/#feat=async-functions),\nbut you can also use [Babel](http://babeljs.io) to compile them down to\nsomething that works in every browser.\n\n##### `\u003cSubscribe\u003e`\n\nNext we'll need a piece to introduce our state back into the tree so that:\n\n* When state changes, our components re-render.\n* We can depend on our container's state.\n* We can call methods on our container.\n\nFor this we have the `\u003cSubscribe\u003e` component which allows us to pass our\ncontainer classes/instances and receive instances of them in the tree.\n\n```jsx\nfunction Counter() {\n  return (\n    \u003cSubscribe to={[CounterContainer]}\u003e\n      {counter =\u003e (\n        \u003cdiv\u003e\n          \u003cspan\u003e{counter.state.count}\u003c/span\u003e\n          \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n          \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n        \u003c/div\u003e\n      )}\n    \u003c/Subscribe\u003e\n  );\n}\n```\n\n`\u003cSubscribe\u003e` will automatically construct our container and listen for changes.\n\n##### `\u003cProvider\u003e`\n\nThe final piece that we'll need is something to store all of our instances\ninternally. For this we have `\u003cProvider\u003e`.\n\n```jsx\nrender(\n  \u003cProvider\u003e\n    \u003cCounter /\u003e\n  \u003c/Provider\u003e\n);\n```\n\nWe can do some interesting things with `\u003cProvider\u003e` as well like dependency\ninjection:\n\n```jsx\nlet counter = new CounterContainer();\n\nrender(\n  \u003cProvider inject={[counter]}\u003e\n    \u003cCounter /\u003e\n  \u003c/Provider\u003e\n);\n```\n\n### Testing\n\nWhenever we consider the way that we write the state in our apps we should be\nthinking about testing.\n\nWe want to make sure that our state containers have a clean way\n\nWell because our containers are very simple classes, we can construct them in\ntests and assert different things about them very easily.\n\n```js\ntest('counter', async () =\u003e {\n  let counter = new CounterContainer();\n  assert(counter.state.count === 0);\n\n  await counter.increment();\n  assert(counter.state.count === 1);\n\n  await counter.decrement();\n  assert(counter.state.count === 0);\n});\n```\n\nIf we want to test the relationship between our container and the component\nwe can again construct our own instance and inject it into the tree.\n\n```js\ntest('counter', async () =\u003e {\n  let counter = new CounterContainer();\n  let tree = render(\n    \u003cProvider inject={[counter]}\u003e\n      \u003cCounter /\u003e\n    \u003c/Provider\u003e\n  );\n\n  await click(tree, '#increment');\n  assert(counter.state.count === 1);\n\n  await click(tree, '#decrement');\n  assert(counter.state.count === 0);\n});\n```\n\nDependency injection is useful in many ways. Like if we wanted to stub out a\nmethod in our state container we can do that painlessly.\n\n```js\ntest('counter', async () =\u003e {\n  let counter = new CounterContainer();\n  let inc = stub(counter, 'increment');\n  let dec = stub(counter, 'decrement');\n\n  let tree = render(\n    \u003cProvider inject={[counter]}\u003e\n      \u003cCounter /\u003e\n    \u003c/Provider\u003e\n  );\n\n  await click(tree, '#increment');\n  assert(inc.calls.length === 1);\n  assert(dec.calls.length === 0);\n});\n```\n\nWe don't even have to do anything to clean up after ourselves because we just\nthrow everything out afterwards.\n\n## FAQ\n\n#### What state should I put into Unstated?\n\nThe React community has focused a lot on trying to put all their state in one\nplace. You could keep doing that with Unstated, but I wouldn't recommend it.\n\nI would recommend a multi-part solution.\n\nFirst, use local component state as much as you possibly can. That counter\nexample from above never should have been refactored away from component\nstate, it was fine before Unstated.\n\nSecond, use libraries to abstract away the bits of state that you'll repeat\nover and over.\n\nLike if form state has you down, you might want to use a library like\n[Final Form](https://github.com/final-form/react-final-form).\n\nIf fetching data is getting to be too much, maybe try out [Apollo](https://www.apollographql.com).\nOr even something uncool but familiar and reliable like [Backbone models and collections](http://backbonejs.org).\nWhat? Are you too cool to use an old framework?\n\nThird, a lot of shared state between components is localized to a few\ncomponents in the tree.\n\n```jsx\n\u003cTabs\u003e\n  \u003cTab\u003eOne\u003c/Tab\u003e\n  \u003cTab\u003eTwo\u003c/Tab\u003e\n  \u003cTab\u003eThree\u003c/Tab\u003e\n\u003c/Tabs\u003e\n```\n\nFor this, I recommend using React's built-in `React.createContext()` API\nand being careful in designing the API for the base components you create.\n\n\u003e **Note:** If you're on an old version of React and want to use the new\n\u003e context API, [I've got you](https://github.com/thejameskyle/create-react-context/)\n\nFinally, (and only after other things are exhausted), if you really need\nsome global state to be shared throughout your app, you can use Unstated.\n\nI know all of this might sound somehow more complicated, but it's a\nmatter of using the right tool for the job and not forcing a single\nparadigm on the entire universe.\n\nUnstated isn't ambitious, use it as you need it, it's nice and small for\nthat reason. Don't think of it as a \"Redux killer\". Don't go trying to\nbuild complex tools on top of it. Don't reinvent the wheel. Just try it\nout and see how you like it.\n\n#### Passing your own instances directly to `\u003cSubscribe to\u003e`\n\nIf you want to use your own instance of a container directly to `\u003cSubscribe\u003e`\nand you don't care about dependency injection, you can do so:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nlet counter = new CounterContainer();\n\nfunction Counter() {\n  return (\n    \u003cSubscribe to={[counter]}\u003e\n      {counter =\u003e \u003cdiv\u003e...\u003c/div\u003e}\n    \u003c/Subscribe\u003e\n  );\n}\n```\n\nYou just need to keep a couple things in mind:\n\n1. You are opting out of dependency injection, you won't be able to\n   `\u003cProvider inject\u003e` another instance in your tests.\n2. Your instance will be local to whatever `\u003cSubscribe\u003e`'s you pass it to, you\n   will end up with multiple instances of your container if you don't pass the\n   same reference in everywhere.\n\nAlso remember that it is _okay_ to use `\u003cProvider inject\u003e` in your application\ncode, you can pass your instance in there. It's probably better to do that in\nmost scenarios anyways (cause then you get dependency injection and all that\ngood stuff).\n\n#### How can I pass in options to my container?\n\nA good pattern for doing this might be to add a constructor to your container\nwhich accepts `props` sorta like React components. Then create your own\ninstance of your container and pass it into `\u003cProvider inject\u003e`.\n\n```jsx\nclass CounterContainer extends Container {\n  constructor(props = {}) {\n    super();\n    this.state = {\n      amount: props.initialAmount || 1,\n      count: 0\n    };\n  }\n\n  increment = () =\u003e {\n    this.setState({ count: this.state.count + this.state.amount });\n  };\n}\n\nlet counter = new CounterContainer({\n  initialAmount: 5\n});\n\nrender(\n  \u003cProvider inject={[counter]}\u003e\n    \u003cCounter /\u003e\n  \u003c/Provider\u003e\n);\n```\n\n## Related\n\n- [unstated-debug](https://github.com/sindresorhus/unstated-debug) - Debug your Unstated containers with ease\n \n","funding_links":[],"categories":["JavaScript","react","数据流","Official","目录","Libraries","⚛️ React","List"],"sub_categories":["macros","State management"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamiebuilds%2Funstated","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamiebuilds%2Funstated","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamiebuilds%2Funstated/lists"}