{"id":13469636,"url":"https://github.com/cassiozen/useStateMachine","last_synced_at":"2025-03-26T07:30:56.596Z","repository":{"id":44624533,"uuid":"347174294","full_name":"cassiozen/useStateMachine","owner":"cassiozen","description":"The \u003c1 kb state machine hook for React","archived":false,"fork":false,"pushed_at":"2024-04-22T04:59:24.000Z","size":2362,"stargazers_count":2377,"open_issues_count":13,"forks_count":47,"subscribers_count":19,"default_branch":"main","last_synced_at":"2024-10-29T15:34:13.053Z","etag":null,"topics":["hooks","react-hook","state-machine","state-management","statemachine","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/cassiozen.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}},"created_at":"2021-03-12T19:20:58.000Z","updated_at":"2024-10-23T23:05:45.000Z","dependencies_parsed_at":"2024-04-22T05:47:45.090Z","dependency_job_id":"a50f08ab-35e1-4e64-82a5-27f9d25c58b4","html_url":"https://github.com/cassiozen/useStateMachine","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiozen%2FuseStateMachine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiozen%2FuseStateMachine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiozen%2FuseStateMachine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiozen%2FuseStateMachine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cassiozen","download_url":"https://codeload.github.com/cassiozen/useStateMachine/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222121829,"owners_count":16934973,"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":["hooks","react-hook","state-machine","state-management","statemachine","typescript"],"created_at":"2024-07-31T15:01:48.055Z","updated_at":"2024-10-29T22:31:22.662Z","avatar_url":"https://github.com/cassiozen.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://user-images.githubusercontent.com/33676/111815108-4695b900-88a9-11eb-8b61-3c45b40d4df6.png\" width=\"250\" alt=\"\"/\u003e\n\u003c/p\u003e\n\n**The \u003c1 kb _state machine_ hook for React:**\n\nSee the user-facing docs at: [usestatemachine.js.org](https://usestatemachine.js.org/)\n\n- Batteries Included: Despite the tiny size, useStateMachine is feature complete (Entry/exit callbacks, Guarded transitions \u0026 Extended State - Context)\n- Amazing TypeScript experience: Focus on automatic type inference (auto completion for both TypeScript \u0026 JavaScript users without having to manually define the typings) while giving you the option to specify and augment the types for context \u0026 events.\n- Made for React: useStateMachine follow idiomatic React patterns you and your team are already familiar with. (The library itself is actually a thin wrapper around React's useReducer \u0026 useEffect.)\n\n\u003cimg width=\"354\" alt=\"size badge\" src=\"https://user-images.githubusercontent.com/33676/126902516-51f46526-3023-43c7-afd4-17df2e89a3a1.png\"\u003e\n\n\n## Examples\n\n- State-driven UI (Hiding and showing UI Elements based on the state) - [CodeSandbox](https://codesandbox.io/s/github/cassiozen/usestatemachine/tree/main/examples/timer?file=/index.tsx) - [Source](./examples/timer)\n- Async orchestration (Fetch data with limited retry) - [CodeSandbox](https://codesandbox.io/s/github/cassiozen/usestatemachine/tree/main/examples/fetch?file=/index.tsx) - [Source](./examples/fetch)\n- Sending data with events (Form) - [CodeSandbox](https://codesandbox.io/s/github/cassiozen/usestatemachine/tree/main/examples/form?file=/index.tsx) - [Source](./examples/form)\n\n## Installation\n\n```bash\nnpm install @cassiozen/usestatemachine\n```\n\n## Sample Usage\n\n```typescript\nimport useStateMachine from \"@cassiozen/usestatemachine\";\n\nconst [state, send] = useStateMachine({\n  initial: 'inactive',\n  states: {\n    inactive: {\n      on: { TOGGLE: 'active' },\n    },\n    active: {\n      on: { TOGGLE: 'inactive' },\n      effect() {\n        console.log('Just entered the Active state');\n        // Same cleanup pattern as `useEffect`:\n        // If you return a function, it will run when exiting the state.\n        return () =\u003e console.log('Just Left the Active state');\n      },\n    },\n  },\n});\n\nconsole.log(state); // { value: 'inactive', nextEvents: ['TOGGLE'] }\n\n// Refers to the TOGGLE event name for the state we are currently in.\n\nsend('TOGGLE');\n\n// Logs: Just entered the Active state\n\nconsole.log(state); // { value: 'active', nextEvents: ['TOGGLE'] }\n```\n\n\n# API\n\n# useStateMachine\n\n```typescript\nconst [state, send] = useStateMachine(/* State Machine Definition */);\n```\n\n`useStateMachine` takes a JavaScript object as the state machine definition. It returns an array consisting of a `current machine state` object and a `send` function to trigger transitions.\n\n### state\n\nThe machine's `state` consists of 4 properties: `value`, `event`, `nextEvents` and `context`.\n\n`value` (string): Returns the name of the current state.\n\n`event` (`{type: string}`; Optional): The name of the last sent event that led to this state.\n\n`nextEvents` (`string[]`): An array with the names of available events to trigger transitions from this state.\n\n`context`: The state machine extended state. See \"Extended State\" below.\n\n### Send events\n\n`send` takes an event as argument, provided in shorthand string format (e.g. \"TOGGLE\") or as an event object (e.g. `{ type: \"TOGGLE\" }`)\n\nIf the current state accepts this event, and it is allowed (see guard), it will change the state machine state and execute effects.\n\nYou can also send additional data with your event using the object notation (e.g. `{ type: \"UPDATE\" value: 10 }`). Check [schema](#schema-context--event-typing) for more information about strong typing the additional data.\n\n# State Machine definition\n\n| Key         | Required | Description |\n| ----------- | ---- |----------- |\n| verbose     |   | If true, will log every context \u0026 state changes. Log messages will be stripped out in the production build. |\n| schema      |   | For usage with TypeScript only. Optional strongly-typed context \u0026 events. More on schema [below](#schema-context--event-typing) |\n| context     |   | Context is the machine's extended state. More on extended state [below](#extended-state-context) |\n| initial     | * | The initial state node this machine should be in |\n| states      | * | Define the possible finite states the state machine can be in. |\n\n## Defining States\n\nA finite state machine can be in only one of a finite number of states at any given time. As an application is interacted with, events cause it to change state.\n\nStates are defined with the state name as a key and an object with two possible keys: `on` (which events this state responds to) and `effect` (run arbitrary code when entering or exiting this state):\n\n### On (Events \u0026 transitions)\n\nDescribes which events this state responds to (and to which other state the machine should transition to when this event is sent):\n\n```typescript\nstates: {\n  inactive: {\n    on: {\n      TOGGLE: 'active';\n    }\n  },\n  active: {\n    on: {\n      TOGGLE: 'inactive';\n    }\n  },\n},\n```\n\nThe event definition can also use the extended, object syntax, which allows for more control over the transition (like adding guards):\n\n```js\non: {\n  TOGGLE: {\n    target: 'active',\n  },\n};\n```\n\n#### Guards\n\nGuards are functions that run before actually making the state transition: If the guard returns false the transition will be denied.\n\n```js\nconst [state, send] = useStateMachine({\n  initial: 'inactive',\n  states: {\n    inactive: {\n      on: {\n        TOGGLE: {\n          target: 'active',\n          guard({ context, event }) {\n            // Return a boolean to allow or block the transition\n          },\n        },\n      },\n    },\n    active: {\n      on: { TOGGLE: 'inactive' },\n    },\n  },\n});\n```\n\nThe guard function receives an object with the current context and the event. The event parameter always uses the object format (e.g. `{ type: 'TOGGLE' }`).\n\n### Effects (entry/exit callbacks)\n\nEffects are triggered when the state machine enters a given state. If you return a function from your effect, it will be invoked when leaving that state (similarly to how useEffect works in React).\n\n```typescript\nconst [state, send] = useStateMachine({\n  initial: 'active',\n  states: {\n    active: {\n      on: { TOGGLE: 'inactive' },\n      effect({ send, setContext, event, context }) {\n        console.log('Just entered the Active state');\n        return () =\u003e console.log('Just Left the Active state');\n      },\n    },\n  },\n});\n```\n\nThe effect function receives an object as parameter with four keys:\n\n- `send`: Takes an event as argument, provided in shorthand string format (e.g. \"TOGGLE\") or as an event object (e.g. `{ type: \"TOGGLE\" }`)\n- `setContext`: Takes an updater function as parameter to set a new context (more on context below). Returns an object with `send`, so you can set the context and send an event on a single line.\n- `event`: The event that triggered a transition to this state. (The event parameter always uses the object format (e.g. `{ type: 'TOGGLE' }`).).\n- `context` The context at the time the effect runs.\n\nIn this example, the state machine will always send the \"RETRY\" event when entering the error state:\n\n```typescript\nconst [state, send] = useStateMachine({\n  initial: 'loading',\n  states: {\n    /* Other states here... */\n    error: {\n      on: {\n        RETRY: 'load',\n      },\n      effect({ send }) {\n        send('RETRY');\n      },\n    },\n  },\n});\n```\n\n## Extended state (context)\n\nBesides the finite number of states, the state machine can have extended state (known as context).\n\nYou can provide the initial context value in the state machine definition, then use the `setContext` function within your effects to change the context:\n\n```js\nconst [state, send] = useStateMachine({\n  context: { toggleCount: 0 },\n  initial: 'inactive',\n  states: {\n    inactive: {\n      on: { TOGGLE: 'active' },\n    },\n    active: {\n      on: { TOGGLE: 'inactive' },\n      effect({ setContext }) {\n        setContext(context =\u003e ({ toggleCount: context.toggleCount + 1 }));\n      },\n    },\n  },\n});\n\nconsole.log(state); // { context: { toggleCount: 0 }, value: 'inactive', nextEvents: ['TOGGLE'] }\n\nsend('TOGGLE');\n\nconsole.log(state); // { context: { toggleCount: 1 }, value: 'active', nextEvents: ['TOGGLE'] }\n```\n\n## Schema: Context \u0026 Event Typing\n\nTypeScript will automatically infer your context type; event types are generated automatically.\n\nStill, there are situations where you might want explicit control over the `context` and `event` types: You can provide you own typing using the `t` whithin `schema`:\n\n*Typed Context example*\n\n```typescript\nconst [state, send] = useStateMachine({\n  schema: {\n    context: t\u003c{ toggleCount: number }\u003e()\n  },\n  context: { toggleCount: 0 },\n  initial: 'inactive',\n  states: {\n    inactive: {\n      on: { TOGGLE: 'active' },\n    },\n    active: {\n      on: { TOGGLE: 'inactive' },\n      effect({ setContext }) {\n        setContext(context =\u003e ({ toggleCount: context.toggleCount + 1 }));\n      },\n    },\n  },\n});\n```\n\n*Typed Events*\n\n\nAll events are type-infered by default, both in the string notation (`send(\"UPDATE\")`) and the object notation (`send({ type: \"UPDATE\"})`). \n\nIf you want, though, you can augment an already typed event to include arbitrary data (which can be useful to provide values to be used inside effects or to update the context). Example:\n\n```typescript\nconst [machine, send] = useStateMachine({\n  schema: {\n    context: t\u003c{ timeout?: number }\u003e(),\n    events: {\n      PING: t\u003c{ value: number }\u003e()\n    }\n  },\n  context: {timeout: undefined},\n  initial: 'waiting',\n  states: {\n    waiting: {\n      on: {\n        PING: 'pinged'\n      }\n    },\n    pinged: {\n      effect({ setContext, event }) {\n        setContext(c =\u003e ({ timeout: event?.value ?? 0 }));\n      },\n    }\n  },\n});\n\nsend({ type: 'PING', value: 150 })\n```\n\n**Note** that you don't need to declare all your events in the schema, only the ones you're adding arbitrary keys and values.\n\n\n# Wiki\n\n- [Updating from version 0.x.x to 1.0](https://github.com/cassiozen/useStateMachine/wiki/Updating-from-0.X.X-to-1.0.0)\n- [Contributing](https://github.com/cassiozen/useStateMachine/wiki/Contributing-to-useStateMachine)\n- [Comparison with XState](https://github.com/cassiozen/useStateMachine/wiki/XState-comparison)\n- [Source code walkthrough video](https://github.com/cassiozen/useStateMachine/wiki/Source-code-walkthrough-video)\n- [Usage with Preact](https://github.com/cassiozen/useStateMachine/wiki/Usage-with-Preact)\n\n# Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://YouTube.com/ReactCasts\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/33676?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eCassio Zen\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=cassiozen\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=cassiozen\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=cassiozen\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#ideas-cassiozen\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/issues?q=author%3Acassiozen\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/devanshj\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/30295578?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eDevansh Jethmalani\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=devanshj\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=devanshj\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#ideas-devanshj\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/issues?q=author%3Adevanshj\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/RunDevelopment\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/20878432?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMichael Schmidt\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=RunDevelopment\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=RunDevelopment\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#ideas-RunDevelopment\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://icyjoseph.dev/\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/21013447?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJoseph\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=icyJoseph\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/mutewinter\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/305901?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJeremy Mack\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=mutewinter\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/devronhansen\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/20226404?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eRon\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=devronhansen\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://v01.io\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/32771?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eKlaus Breyer\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=klausbreyer\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://linktr.ee/arthurdenner\"\u003e\u003cimg src=\"https://avatars.githubusercontent.com/u/13774309?v=4?s=100\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eArthur Denner\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=arthurdenner\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/issues?q=author%3Aarthurdenner\" title=\"Bug reports\"\u003e🐛\u003c/a\u003e \u003ca href=\"https://github.com/cassiozen/useStateMachine/commits?author=arthurdenner\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#ideas-arthurdenner\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-restore --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","funding_links":[],"categories":["TypeScript","Frameworks"],"sub_categories":["State of the React"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiozen%2FuseStateMachine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcassiozen%2FuseStateMachine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiozen%2FuseStateMachine/lists"}