{"id":13452498,"url":"https://github.com/suchipi/react-state-tree","last_synced_at":"2025-10-07T17:04:40.626Z","repository":{"id":51777517,"uuid":"177095655","full_name":"suchipi/react-state-tree","owner":"suchipi","description":"Drop-in replacement for useState that persists your state into a redux-like state tree","archived":false,"fork":false,"pushed_at":"2021-05-10T02:56:00.000Z","size":500,"stargazers_count":102,"open_issues_count":8,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-14T16:13:30.419Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/suchipi.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":"2019-03-22T07:50:54.000Z","updated_at":"2025-03-30T00:45:31.000Z","dependencies_parsed_at":"2022-08-22T21:10:19.437Z","dependency_job_id":null,"html_url":"https://github.com/suchipi/react-state-tree","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suchipi%2Freact-state-tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suchipi%2Freact-state-tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suchipi%2Freact-state-tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suchipi%2Freact-state-tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suchipi","download_url":"https://codeload.github.com/suchipi/react-state-tree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248914115,"owners_count":21182359,"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-31T07:01:25.841Z","updated_at":"2025-10-07T17:04:35.557Z","avatar_url":"https://github.com/suchipi.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# `react-state-tree`\n\n`react-state-tree` is a drop-in replacement for `useState` that persists your state into a redux-like state tree stored in a top-level provider component.\n\nIf you use `useStateTree` in an app with no `StateTreeProvider`, it will fall back to `useState`. So it is safe to use in custom hooks and components.\n\n## Tutorial\n\nTo start using `react-state-tree`, wrap your app with a `StateTreeProvider`:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { StateTreeProvider } from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e {/* added StateTreeProvider */}\n      \u003cCounter /\u003e\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nNow, use `useStateTree` instead of `useState` every time you want to persist state to the tree:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { useStateTree } from \"react-state-tree\";\n\nfunction Counter({ id }) {\n  const [count, setCount] = useStateTree(0); // replaced useState with useStateTree\n\n  const handleClick = () =\u003e setCount(count + 1);\n  return \u003cbutton onClick={handleClick}\u003e{count}\u003c/button\u003e;\n}\n```\n\nYour app state is now stored within the `StateTreeProvider`; try adding an `onUpdate` callback to your `StateTreeProvider`:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nfunction App() {\n  return (\n    \u003cStateTreeProvider onUpdate={(state) =\u003e console.log(state)}\u003e {/* added onUpdate prop */}\n      \u003cCounter /\u003e\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nYou can now look in the console to see your state:\n\n\u003c!-- prettier-ignore --\u003e\n```js\n// Sample log output\n{ $0: 1 }\n{ $0: 2 }\n{ $0: 3 }\n```\n\nIt's kind of ugly, though. Let's customize that key by adding a second argument to the `useStateTree` call:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nfunction Counter({ id }) {\n  const [count, setCount] = useStateTree(0, \"count\"); // added \"count\" as the second argument\n\n  const handleClick = () =\u003e setCount(count + 1);\n  return \u003cbutton onClick={handleClick}\u003e{count}\u003c/button\u003e;\n}\n```\n\nNow if we look at the log output:\n\n\u003c!-- prettier-ignore --\u003e\n```js\n{ count: 1 }\n{ count: 2 }\n{ count: 3 }\n```\n\nA lot nicer. But what if we want multiple counters in our app?\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nfunction App() {\n  return (\n    \u003cStateTreeProvider onUpdate={(state) =\u003e console.log(state)}\u003e\n      \u003cCounter /\u003e\n      \u003cCounter /\u003e {/* added another Counter */}\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nWith our custom key, they'll both try to write to `count`, which won't work...\n\nIf we remove the custom key, it'll work, but it'll be ugly again:\n\n\u003c!-- prettier-ignore --\u003e\n```js\n{ $0: 1, $1: 0 }\n{ $0: 1, $1: 1 }\n{ $0: 1, $1: 2 }\n```\n\nHow can we keep our nice custom `count` key but use a different part of the state tree for each counter?\n\nOne way is to wrap each counter with the `StateKey` component:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { StateTreeProvider, StateKey } from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider onUpdate={(state) =\u003e console.log(state)}\u003e\n      \u003cStateKey value=\"first\"\u003e {/* added StateKey around Counter */}\n        \u003cCounter /\u003e\n      \u003c/StateKey\u003e\n      \u003cStateKey value=\"second\"\u003e {/* added StateKey around Counter */}\n        \u003cCounter /\u003e\n      \u003c/StateKey\u003e\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nAnd now we can put our custom key back:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nfunction Counter({ id }) {\n  const [count, setCount] = useStateTree(0, \"count\"); // put count back\n\n  const handleClick = () =\u003e setCount(count + 1);\n  return \u003cbutton onClick={handleClick}\u003e{count}\u003c/button\u003e;\n}\n```\n\nAnd now our state looks like this:\n\n\u003c!-- prettier-ignore --\u003e\n```js\n{ first: { count: 1 }, second: {count: 0 } }\n{ first: { count: 1 }, second: {count: 1 } }\n{ first: { count: 1 }, second: {count: 2 } }\n```\n\nNeat! But what if we want them in an array instead of under the `first` and `second` keys?\n\nTo do that, we use `StateKeyList` instead of `StateKey`:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { StateTreeProvider, StateKey } from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider onUpdate={(state) =\u003e console.log(state)}\u003e\n      \u003cStateKeyList value=\"counters\"\u003e {/* removed StateKeys and added StateKeyList instead */}\n        \u003cCounter /\u003e\n        \u003cCounter /\u003e\n      \u003c/StateKeyList\u003e\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nNow our state looks like this:\n\n\u003c!-- prettier-ignore --\u003e\n```js\n{\n  counters: [\n    { count: 1 },\n    { count: 0 }\n  ]\n}\n{\n  counters: [\n    { count: 1 },\n    { count: 1 }\n  ]\n}\n{\n  counters: [\n    { count: 1 },\n    { count: 2 }\n  ]\n}\n```\n\nAs you can see, the shape of your state follows the shape of your React tree in your app.\n\nNow that we have all your app's state in one tree, let's try persisting it to `localStorage`:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { StateTreeProvider, StateKey } from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider\n      // Added initialState prop and changed onUpdate prop\n      initialState={localStorage.state ? JSON.parse(localStorage.state) : {}}\n      onUpdate={(state) =\u003e localStorage.state = JSON.stringify(state)}\n    \u003e\n      {/* ... omitted for brevity ... */}\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\nNow click your counters a few times, then refresh the page. The state persists across page refreshes!\n\n## API Documentation\n\nThe `react-state-tree` module has several named exports; each is documented here.\n\n### `StateTreeProvider`\n\n`StateTreeProvider` is a React component that should be wrapped around your app- or at least, the part of your app where you want a state tree.\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport { StateTreeProvider } from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e\n      {/* rest of your app goes here... */}\n    \u003c/StateTreeProvider\u003e\n  );\n}\n```\n\n`StateTreeProvider` accepts two optional props (in addition to `children`):\n\n- `initialValue` - The initial value for the state tree. Defaults to `{}`.\n- `onUpdate` - A function to call whenever the state tree changes. It will be called with a single argument: the state tree.\n\n### `useStateTree`\n\n`useStateTree` is a React hook that behaves the same as `useState`, but the state is stored on the state tree instead of in component state.\n\nIts signature is the same as `useState`:\n\n```jsx\nimport { useStateTree } from \"react-state-tree\";\n\nconst [count, setCount] = useStateTree(0);\n```\n\nBut it also accepts an optional second argument, the key to persist the state under:\n\n```jsx\nimport { useStateTree } from \"react-state-tree\";\n\nconst [count, setCount] = useStateTree(0, \"count\");\n```\n\nIf you don't pass the second argument, it will us an autogenerated key by default (unless you use `nextStateTreeKey`; see the docs for that function below for more details).\n\n### `StateKey`\n\nThis component wraps a portion of your app and changes where all `useStateTree` calls in child components link to on the state tree.\n\nWhen you don't have a `StateKey` in your app, all `useStateTree` calls will link to the top object in the state tree:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport {useStateKey, StateTreeProvider} from \"react-state-tree\";\n\n// Given this component:\nfunction Counter() {\n  const [count, setCount] = useStateTree(0, \"count\");\n\n  return (\n    \u003cbutton\n      onClick={() =\u003e setCount(count + 1)}\n    \u003e\n      {count}\n    \u003c/button\u003e\n  );\n}\n\n// And this app:\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e\n      \u003cCounter /\u003e\n    \u003c/StateTreeProvider\u003e\n  )\n}\n\n// The state tree will look like this:\n// { count: 0 }\n```\n\nBut if you wrap a portion of your app with a `StateKey`, then all of the state for child components of that `StateKey` are linked to a sub-tree of your state tree:\n\n```jsx\nimport { useStateKey, StateTreeProvider, StateKey } from \"react-state-tree\";\n\n// If you wrap the same Counter from above with a StateKey:\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e\n      \u003cStateKey value=\"counter\"\u003e\n        \u003cCounter /\u003e\n      \u003c/StateKey\u003e\n    \u003c/StateTreeProvider\u003e\n  );\n}\n\n// Then all of that Counter's state is moved underneath the \"counter\" key in your state tree:\n// { counter: { count: 0 } }\n```\n\nThe `value` prop passed to the `StateKey` determines where child component state gets linked. In the above example, the `value` prop was `\"counter\"`, so the state was moved to `{ counter: ... }` in the state tree.\n\n`StateKey`s compose with parent `StateKeys`, allowing you to create large, deep state trees.\n\n### `StateListKey`\n\n`StateListKey` works the same as `StateKey`, but it creates an Array instead of an object.\n\nSuppose we had an app with two counters in it:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport {useStateKey, StateTreeProvider} from \"react-state-tree\";\n\n// Given this component:\nfunction Counter({ stateKey }) {\n  const [count, setCount] = useStateTree(0, stateKey);\n\n  return (\n    \u003cbutton\n      onClick={() =\u003e setCount(count + 1)}\n    \u003e\n      {count}\n    \u003c/button\u003e\n  );\n}\n\n// And this app:\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e\n      \u003cCounter stateKey=\"first\" /\u003e\n      \u003cCounter stateKey=\"second\" /\u003e\n    \u003c/StateTreeProvider\u003e\n  )\n}\n\n// The state tree will look like this:\n// { first: 0, second: 0 }\n```\n\nIf we wrap the counters in a `StateListKey`, then they will be linked to an array in the state tree instead:\n\n\u003c!-- prettier-ignore --\u003e\n```jsx\nimport {useStateKey, StateTreeProvider, StateListKey} from \"react-state-tree\";\n\nfunction App() {\n  return (\n    \u003cStateTreeProvider\u003e\n      \u003cStateListKey value=\"counters\"\u003e\n        \u003cCounter stateKey=\"first\" /\u003e\n        \u003cCounter stateKey=\"second\" /\u003e\n      \u003c/StateListKey\u003e\n    \u003c/StateTreeProvider\u003e\n  )\n}\n\n// Each Counter's state is moved into an object that lives in an array at the \"counters\" key in your state tree:\n// {\n//   counters: [\n//     { first: 0 },\n//     { second: 0 }\n//   ]\n// }\n\n// Since they're in separate objects now, it probably makes sense to remove the `stateKey` prop from `Counter` and always put the state at `count` instead:\nfunction Counter() {\n  const [count, setCount] = useStateTree(0, \"count\");\n\n  return (\n    \u003cbutton\n      onClick={() =\u003e setCount(count + 1)}\n    \u003e\n      {count}\n    \u003c/button\u003e\n  );\n}\n\n// With that change in place, here's how the state tree will look:\n// {\n//   counters: [\n//     { count: 0 },\n//     { count: 0 }\n//   ]\n// }\n```\n\nThe `value` prop passed to the `StateListKey` determines where the array for child component states will get linked. In the above example, the `value` prop was `\"counters\"`, so the state was linked to `{ counters: [...] }` in the state tree.\n\n### `nextStateTreeKey`\n\n`nextStateTreeKey` is an escape-hatch that lets you specify the state key for the next `useStateTree` call.\n\nSuppose you had a custom hook that created some state:\n\n```jsx\nfunction usePosition(leftKey, rightKey) {\n  const [position, setPosition] = useStateTree({ x: 0, y: 0 });\n\n  // ... and a bunch of custom logic that\n  // calls `setPosition` based on user input,\n  // using `leftKey` and `rightKey`.\n\n  return position;\n}\n```\n\nAnd you were using that hook multiple times in a component:\n\n```jsx\nfunction MyComponent() {\n  const player1Pos = usePosition(\"ArrowLeft\", \"ArrowRight\");\n  const player2Pos = usePosition(\"a\", \"d\");\n\n  // ... rest of the component omitted\n}\n```\n\nIf you want to specify a state key for each `position`, you can't do so unless you modify `usePosition` to accept a state key:\n\n```jsx\nfunction usePosition(leftKey, rightKey, stateKey) {\n  const [position, setPosition] = useStateTree({ x: 0, y: 0 }, stateKey);\n\n  // ...\n}\n\nfunction MyComponent() {\n  const player1Pos = usePosition(\"ArrowLeft\", \"ArrowRight\", \"player1Position\");\n  const player2Pos = usePosition(\"a\", \"d\", \"player2Position\");\n\n  // ...\n}\n```\n\nThis works fine, but if you have a lot of nested hooks, it can be tedious to thread the custom key all the way down to where the `useStateTree` call is.\n\nAs a workaround, you can use the `nextStateTreeKey` function to specify the key for the next `useStateTree` call:\n\n```jsx\nfunction usePosition(leftKey, rightKey) {\n  const [position, setPosition] = useStateTree({ x: 0, y: 0 });\n\n  // ...\n}\n\nfunction MyComponent() {\n  nextStateTreeKey(\"player1Position\");\n  const player1Pos = usePosition(\"ArrowLeft\", \"ArrowRight\");\n\n  nextStateTreeKey(\"player2Position\");\n  const player2Pos = usePosition(\"a\", \"d\");\n\n  // ...\n}\n```\n\nHowever, use this sparingly; it creates an implicit contract that the next custom hook will use `useStateTree` at some point, which can be dangerous. If `usePosition` was changed later to not use `useStateTree`, then `nextStateTreeKey` could affect some other unrelated `useStateTree` call elsewhere in the app (whichever `useStateTree` call happens next, as long as it doesn't have a key specified).\n\nNote that `nextStateTreeKey` has no effect on `useStateTree` calls that have a state key specified as their second argument; `nextStateTreeKey` _only_ affects `useStateTree` calls without a second argument. If you call `nextStateTreeKey` before a `useStateTree` call, but that `useStateTree` call had a second argument specified, then the key passed into `nextStateTreeKey` will be unused, because the second argument to `useStateTree` will be used instead.\n\n### `useEntireStateTree`\n\n`useEntireStateTree` is an escape hatch that gives you the entire state tree, and the ability to change it. It's useful if you want to create a component that shows the state tree (for debugging), or you want to implement a redux-style `dispatch` pattern on top of `react-state-tree`.\n\nWhen `useEntireStateTree` is called, it returns an object with two keys: `stateTree` and `replaceStateTree`.\n\n- `stateTree` is the entire state tree.\n- `replaceStateTree` is a function that can be called with a new state tree object to replace the state tree. Think of it as `setState` for the whole state tree.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuchipi%2Freact-state-tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuchipi%2Freact-state-tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuchipi%2Freact-state-tree/lists"}