{"id":45879165,"url":"https://github.com/humaans/react-machine","last_synced_at":"2026-02-27T13:18:49.619Z","repository":{"id":55009785,"uuid":"321642271","full_name":"humaans/react-machine","owner":"humaans","description":"A state machine hook for React applications.","archived":false,"fork":false,"pushed_at":"2024-05-01T19:31:29.000Z","size":1607,"stargazers_count":110,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-22T15:22:37.152Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/humaans.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-12-15T11:08:26.000Z","updated_at":"2025-02-20T20:24:37.000Z","dependencies_parsed_at":"2024-05-01T12:53:25.333Z","dependency_job_id":"714e2b70-e6d0-47be-8c9a-0000c94d320b","html_url":"https://github.com/humaans/react-machine","commit_stats":{"total_commits":59,"total_committers":1,"mean_commits":59.0,"dds":0.0,"last_synced_commit":"953333e1d3644a68beeb6c4d688eac6cf3a51181"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/humaans/react-machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humaans%2Freact-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humaans%2Freact-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humaans%2Freact-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humaans%2Freact-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/humaans","download_url":"https://codeload.github.com/humaans/react-machine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/humaans%2Freact-machine/sbom","scorecard":{"id":473195,"data":{"date":"2025-08-11","repo":{"name":"github.com/humaans/react-machine","commit":"1643990170794ddae2a0722d6991f899625450eb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/21 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T14:30:50.002Z","repository_id":55009785,"created_at":"2025-08-19T14:30:50.002Z","updated_at":"2025-08-19T14:30:50.002Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29896328,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T12:09:13.686Z","status":"ssl_error","status_checked_at":"2026-02-27T12:09:13.282Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":"2026-02-27T13:18:48.937Z","updated_at":"2026-02-27T13:18:49.607Z","avatar_url":"https://github.com/humaans.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"360\" src=\"https://user-images.githubusercontent.com/324440/102810325-6ce7ad80-43bb-11eb-9a72-9eead02fc71f.png\" alt=\"react machine logo, a man with an open head with a state chart inside\" title=\"react-machine\"\u003e\n\u003c/p\u003e\n\n\n\u003ch4 align=\"center\"\u003eFinite state machine hook for React\u003c/h4\u003e\n\u003cbr /\u003e\n\nWhen `useState` or `useReducer` is not enough, `useMachine` hook can be used to express more complex component state and business logic. Machines are especially useful for handling asynchronous effects in your components, for example, saving a form.\n\nFeatures include:\n\n- a single `useMachine` hook for declaratively describing state machines\n- define `states` and `transitions` between states\n- apply `reducers` and queue `effects` when transitioning\n- `immediate` transitions with `guards`\n- `internal` transitions for updating state data or queuing effects\n- first class support for promises with the `invoke` effects\n- internally implemented using `useReducer` and `useEffect` hooks\n- hierarchical and parallel states _(coming in V2 in 2021)_\n- semantics guided by the [SCXML](https://www.w3.org/TR/scxml/) spec _(coming in V2 in 2021)_\n\n### Example\n\n```js\nimport React from 'react'\nimport { useMachine } from 'react-machine'\n\nconst isSuccess = ctx =\u003e ctx.item.status === 'success'\nconst isError = ctx =\u003e ctx.item.status === 'error'\nconst increment = (ctx, data) =\u003e ({ ...data, count: data.count + 1 })\nconst retry = ctx =\u003e ctx.retry()\n\nconst machine = ({ state, transition, immediate, internal, enter }) =\u003e {\n  state(\n    'loading',\n    immediate('counter', { guard: isSuccess }),\n    immediate('error', { guard: isError })\n  )\n\n  state('error',\n    transition('retry', 'loading', { effect: retry })\n  )\n\n  state(\n    'counter',\n    enter({ assign: { count: 0 } }),\n    internal('increment', { reduce: increment })\n  )\n}\n\nexport function Component({ item, retry }) {\n  const { state, send } = useMachine(machine, { item, retry })\n\n  if (state.name === 'loading') return \u003cdiv\u003eLoading\u003c/div\u003e\n  if (state.name === 'error') return \u003cbutton onClick={() =\u003e send('retry')}\u003eRetry\u003c/button\u003e\n  return \u003cbutton onClick={() =\u003e send('increment')}\u003e{state.data.count}\u003c/button\u003e\n}\n```\n\nAlso see:\n\n  * [Example: Form](examples/example-form.md)\n\n## Motivation\n\n`react-machine` is very much inspired by [XState](https://xstate.js.org/) and [Robot](https://thisrobot.life/) - thank you for really great libraries 🙏\n\n### Comparison to [Robot](https://thisrobot.life/)\n\nRobot is a great Finite State Machine library with integrations into React, Preact, Svelte and more. It has a neat succint API that inspired the API of `react-machine` and boasts a lightweight implementation. Here are some differences:\n\n- Robot requires every helper function to be imported individually. `react-machine` uses a similar DSL, but passes helpers as arguments to the machine creation function or takes them as options. This means you only ever need to import `useMachine`, which is more akin to how you'd use `useState` or `useReducer` for managing state.\n- Robot has some support for nesting machines, but `react-machine` (_in the upcoming V2_) will support arbitrarily nested and parallel states, adhering more closely to the [SCXML](https://www.w3.org/TR/scxml/) spec.\n- Robot does not support internal transitions, making it difficult to update machine context in the middle of an async function invocation.\n- Robot does not clean up promise invocations in between transitions.\n- Robot does not have `enter` and `exit` hooks, and does not allow custom effect implementations.\n- Since `react-machine` is built with React in mind, it has a special ability to react to context (or prop) changes to drive machine transitions.\n\n### Comparison to [XState](https://xstate.js.org/):\n\nXState is the most powerful modern state chart / state machine implementation for JavaScript. It's rich in features and supports React and Vue out of the box. Here are some differences:\n\n- `react-machine` strives to create a smaller surface area, less features, less options, less packages. This could be seen as a good or a bad thing depending on your perspective and your requirements. For example, you will not find actors, machine inter messaging, delayed events or history states in `react-machine`.\n- related to the point above, full compatibility / serialisation to SCXML is a non goal for `react-machine`, SCXML (and it's interpration algorithm in particular) is only used to guide the implementation of `react-machine`.\n- `react-machine` uses a more functional machine declaration DSL that is closer to that found in Robot, whereas XState declares machines using a deeply nested object notation, this might well be a personal preference, give both a try, and also XState might gain new optional DSL adapters in the future.\n- XState provides visualisation of it's state charts, a feature that could be added to `react-machine` in the future.\n- Since `react-machine` is built with React in mind, it has a special ability to react to context (or prop) changes to drive machine transitions.\n\n### Conclusion\n\nIn summary, `react-machine` is an experiment in creating a lightweight and flexible solution for component level state management in React. The core machine logic is pure and stateless, which allows for delegating the state storage to `useReducer` and effect execution to `useEffect`, done so with concurrent mode compatibility in mind.\n\n## API\n\nMachines are created using the API passed into machine description function, here's an exhaustive example showing all possible types of transitions and hooks:\n\n```js\nconst { state, send, context, machine } = useMachine(({ state, transition, immediate, internal, enter, exit }) =\u003e {\n    state(stateName,\n      enter({ reduce, assign, invoke, effect }),\n      transition(event, target, { guard, reduce, assign, effect }),\n      immediate(target, { guard, reduce, assign, effect }),\n      internal(event, { guard, reduce, assign, effect }),\n      exit({ reduce, assign, effect }),\n    )\n}, context, options)\n```\n\n### Concepts\n\nWhen you invoke the `useMachine` hook it returns machine state, send, context and the constructed machine itself. Typically you'll use state, send and you could use context in case you're passing it down to other components.\n\n**State** - state consists of `name` and `data` (state data). Name is the name of the current state node the machine is in. State data is any data that has been computed using reducers (and assigners) as part of transitioning between states.\n\n**Send** - this is the main way you will transition machine from one state to another. Send can take the event name, or an event object with key `type` and arbitrary other keys as payload.\n\n**Context** - context is passed as the second argument to `useMachine` and is not to be confused with machine state or machine state data. Context is a little bit like React component props. It's some immutable data that gets passed into every machine hook (such as guard, reduce or effect). Context provides the best way for your machine to utilise component props in machine's business logic. If context changes (or more specifically, any of value of the context object changes), an event of type `assign` and no payload will be sent into the machine, which will evaluate any guarded immediated transitions and any transitions listening to `assign` event. This is very useful if you want your machine to change states or update it's state data based on changing context. It is also efficient, since context changes that are not relevant to the current machine state will not trigger any re-renders.\n\n#### Hook\n\n* [useMachine](#usemachinedescription-context-options)\n\n#### State machine description\n\n* [state](#statename-transitions)\n* [transition](#transitionevent-target-options)\n* [immediate](#immediatetarget-options)\n* [internal](#internalevent-options)\n* [enter](#enteroptions)\n* [exit](#exitoptions)\n* [initial](#initialoptions)\n\n#### Transition hooks\n\n* [guard](#guard)\n* [reduce](#reduce)\n* [assign](#assign)\n* [invoke](#invoke)\n* [effect](#effect)\n\n### `useMachine(description, context, options)`\n\nCreate and initialise the machine.\n\n- `description` - the machine description function invoked with `state`, `transition`, `immediate`, `internal`, `enter`, `exit` and `initial` as arguments.\n- `context` - the context to be assigned to the machine's state. Since it's common to pass props and other computed data via context, by default, whenever any of the values of the context change, the hook will send an event of type `assign` with the context object spread onto the event object, this event can be renamed or disabled in options.\n- `options` - hook options\n\nAvailable options:\n\n- `assign` (default: `\"assign\"`) - the name of the event to be sent when context changes. Set this to `false` to disable sending the event altogether.\n- `areEqual` (default: compare object values) - by default all context values are checked for changes on each render. Use this option to customize how equality is computed when comparing the previous context with the new context.\n\nReturns `{ state, send, context, machine }`:\n\n- `state` - current state of shape `{ name, data, final }`\n- `send` - send an event, e.g. `send('save')` or `send({ type: 'save', item: 'x' })`\n- `context` - the same value that was passed in as the context argument to the hook\n- `machine` - a stateless machine description that is used internally to compute transitions\n\n```js\nconst myMachine = useCallback(({ state, transition, initial }) =\u003e {\n  initial({ x: 0 })\n  state('a', transition('next', 'b'))\n  state('b', transition('next', 'c'))\n  state('c')\n}, [])\nconst  {state, send, context, machine } = useMachine(myMachine, { close: props.close })\nconst { name, data, final } = state\n```\n\n### `state(name, ...transitions)`\n\nDeclare a state.\n\n- `name` - name of the state\n- `transitions` - any number of available: `transition()`, `immediate()`, `internal()`, `enter()`, `exit()`\n\n```js\nstate('loading')\nstate('loading', transition('go', 'ready'))\nstate('loading', immediate('ready', { guard: ctx =\u003e ctx.loaded }))\n```\n\n### `transition(event, target, options)`\n\nDeclare a transition between states.\n\n- `event` - the name of the event that will trigger this transition\n- `target` - the name of the target state\n- `options` - in the shape of `{ reduce, assign, invoke, effect, guard }`\n\n```js\ntransition('save', 'saving')\ntransition('reset', 'edit', { reduce: (ctx, data) =\u003e ({ ...data, value: null }) })\ntransition('close', 'closing', { effect: ctx =\u003e ctx.onClose() })\n```\n\n### `immediate(target, options)`\n\nA special type of transition that is executed immediately upon entering (or re-entering a state with an internal transition). If no `guard` option is used, the transition will always immediately be applied and move the machine to a new state. If the `guard` option is used, the transition will only be applied if the `guard` condition passes. Note that, when immediate transitions take place, all of the intermediate transition hooks and intermediate state enter/exit hooks are triggered, however the effects (including `invoke`) are only executed for the final state, not any of the intermediate states.\n\n- `target` - the name of the target state\n- `options` - in the shape of `{ reduce, assign, invoke, effect, guard }`\n\n```js\nimmediate('ready')\nimmediate('ready', { guard: { guard: ctx =\u003e ctx.loaded } })\n```\n\n### `internal(event, options)`\n\nA special type of transition that does not leave the state and does not trigger any enter/exit hooks. Useful for performing effects or updating context without leaving the state. Note: this transition does re-evaluate all immediate transitions of the state.\n\n- `event` - the name of the event that will trigger this transition\n- `options` - in the shape of `{ reduce, assign, invoke, effect, guard }`\n\n```js\ninternal('assign', { assign: true })\ninternal('reset', { assign: { count: 0 } })\n```\n\n### `enter(options)`\n\nHooks to run when entering a state.\n\n- `options` - in the shape of `{ reduce, assign, invoke, effect }`\n\n```js\nenter({ effect: ctx =\u003e ctx.start() })\nenter({ invoke: (ctx, data) =\u003e ctx.fetch('/item/' + data.id) })\nenter({ assign: { count: 0 } })\n```\n\n### `exit(options)`\n\nHooks to run when leaving the state.\n\n- `options` - in the shape of `{ reduce, assign, effect }`\n\n```js\nexit({ effect: ctx =\u003e ctx.stop() })\nexit({ assign: { error: null } })\n```\n\n### `initial(...)`\n\nA hook for setting initial state and/or initial state data. If initial state name is omitted, the first state node will be used as the initial state. The initial data can be a static object (or any kind of data structure) or a function that takes context and returns the initial data.\n\n```js\ninitial('loading')\ninitial('loading', { item: null })\ninitial('loading', (ctx) =\u003e ({ item: ctx.item }))\ninitial('loading', (ctx) =\u003e { item: ctx.item })\ninitial({ item: null })\ninitial((ctx) =\u003e ({ item: ctx.item }))\ninitial((ctx) =\u003e { item: ctx.item })\n```\n\n### `guard`\n\nIf the guard condition fails, the transition is skipped when matching against the event and selection proceeds to the next transition. Commonly used with `immediate` transitions, but works with any type of transition.\n\n```js\n{ guard: (context, data, event) =\u003e context.status === 'success' }\n```\n\n### `reduce`\n\nUpdated context based on current context and the incoming event.\n\n```js\n{ reduce: (context, data, event) =\u003e nextData }\n{ reduce: (context, data, { type, ...payload }) =\u003e nextData }\n{ reduce: [reduce1, reduce2] }\n```\n\n### `assign`\n\nReturn a partial context update object, that will be immutably assigned to the current context. A commonly useful shortcut for assigning event paylods to the context.\n\n```js\n{ assign: (context, data, { type, ...payload }) =\u003e ({ ...data , ...payload }) }\n{ assign: true } // same as above\n{ assign: { some: 'value' } }\n{ assign: [assign1, assign2] }\n```\n\n### `invoke`\n\nA way to invoke async functions as part of entering a state. If the promise is fulfilled, an event of shape `{ type: 'done', result }` is sent, and if the promise rejects, an event of `{ type: 'error', error }` is sent. Note, if the machine exits the state while the promise is pending, the results will be ignored and no event will get sent. Note, internally, `invoke` is turned into an `effect`.\n\n```js\nstate('save',\n  enter({ invoke: async (context, data, event) =\u003e context.save() }),\n  transition('done', 'show', { assign: (ctx, data, event) =\u003e ({ item: event.result }) }),\n  transition('error', 'edit', { assign: (ctx, data, event) =\u003e ({ error: event.error }) }),\n)\n```\n\n### `effect`\n\nA way of handling side effects, async effects, subscriptions or activities. Once the state is entered, the effect gets started (in `useEffect` and only after finalising all of the immediate transitions) and can send any number of events. Note that `context` will be valid when initially running the effect, but will get stale afterwards. Also note that `send` will be ignored after the effect is cleaned up, and similarly `send` can not be used in the cleanup function of the effect.\n\n```js\nconst addPing = (ctx, data, event) =\u003e ({ pings: data.pings.concat(event.ping) })\nconst listenToPings = (context, data, event, send) =\u003e {\n  const cancel = context.ponger.subscribe((ping) =\u003e {\n    send({ type: 'ping', ping })\n  })\n  return () =\u003e cancel()\n}\n\nstate('save',\n  enter({ assign: { pings: [] }, effect: listenToPings }),\n  internal('ping', { assign: addPing })\n)\n```\n\n### Roadmap\n\n#### V1\n\n- [x] decomplect machine context from state data (see [Changelog](CHANGELOG.md))\n- [x] remove the concept of actions in favor of effects (see [Changelog](CHANGELOG.md))\n- [ ] add an option to enable debug logging\n- [ ] write proper TypeScript type definitions\n\n#### V2\n\n- [ ] add hierarchical and parallel states\n- [ ] introduce initial and final states\n- [ ] change from state.name string to state.value object\n- [ ] introduce matches() api\n\n#### V3\n\n- [ ] add compatibility with XState visualiser, serialize into compatible JSON\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumaans%2Freact-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhumaans%2Freact-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhumaans%2Freact-machine/lists"}