├── .eslintrc.json ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── .yarnrc.yml ├── README.md ├── content ├── metadata.ts └── posts │ ├── 2019-11-13-no-disabling-a-button-is-not-app-logic.mdx │ ├── 2019-12-09-xstate-version-47-and-the-future.mdx │ ├── 2020-01-20-redux-is-half-a-pattern-1-2.mdx │ ├── 2020-05-22-redux-is-half-a-pattern-2-2.mdx │ ├── 2020-05-27-state-management-bad-bolean-good-boolean.mdx │ ├── 2020-07-27-state-machines-how-to-stop-making-horcruxes-in-your-code.mdx │ ├── 2021-01-11-just-use-props-an-opinionated-guide-to-react-and-xstate.mdx │ ├── 2021-01-20-you-dont-need-a-library-for-state-machines.mdx │ ├── 2021-04-28-whats-the-difference-between-machine-and-createmachine.mdx │ ├── 2021-04-29-should-this-be-a-state-or-in-context.mdx │ ├── 2021-04-30-should-this-be-an-action-or-a-service.mdx │ ├── 2021-05-13-why-i-love-invoked-callbacks.mdx │ ├── 2021-05-27-global-state-xstate-react.mdx │ ├── 2021-07-28-usestate-vs-usereducer-vs-xstate-part-1-modals.mdx │ ├── 2021-10-02-intro-fsm-sc.mdx │ ├── 2021-10-11-convince-teammates.mdx │ ├── 2021-12-14-what-are-the-biggest-benefits-youve-had-from-using-state-machines.mdx │ ├── 2022-01-27-introducing-typegen.mdx │ ├── 2022-02-14-modelling-101-how-to-build-a-statechart-from-scratch.mdx │ ├── 2022-03-03-introducing-the-xstate-cli.mdx │ ├── 2022-03-11-xstate-vscode-extension-now-available-on-the-open-vsx-registry.mdx │ ├── 2022-03-15-introducing-the-new-stately-homepage.mdx │ ├── 2022-03-23-stately-changelog-1.mdx │ ├── 2022-03-28-stately-is-hiring.mdx │ ├── 2022-03-29-introducing-the-new-stately-roadmap.mdx │ ├── 2022-04-05-building-a-video-player.mdx │ ├── 2022-05-03-whats-new-may-2022.mdx │ ├── 2022-06-07-whats-new-june-2022.mdx │ ├── 2022-06-13-nesting-typegen-files.mdx │ ├── 2022-06-20-what-is-xstate-used-for.mdx │ ├── 2022-07-06-whats-new-july-2022.mdx │ ├── 2022-07-18-just-use-hooks-xstate-in-react-components.mdx │ ├── 2022-08-03-whats-new-august-2022.mdx │ ├── 2022-08-10-get-started-with-xstate-using-xsm-snippet.mdx │ ├── 2022-08-17-goodbye-use-effect-at-react-next.mdx │ ├── 2022-08-23-building-a-resizable-panel.mdx │ ├── 2022-08-31-building-a-resizable-panel.mdx │ ├── 2022-09-14-whats-new-september-2022.mdx │ ├── 2022-10-18-stately-studio-1-0.mdx │ ├── 2022-10-27-studio-tutorials.mdx │ ├── 2022-11-22-studio-update.mdx │ ├── 2022-11-29-import-from-code.mdx │ ├── 2022-12-13-new-in-the-studio-machine-disaster-recovery.mdx │ ├── 2022-2-6-stately-editor-public-beta.mdx │ ├── 2023-01-19-introducing-the-stately-docs.mdx │ ├── 2023-01-20-whats-new-in-2023.mdx │ ├── 2023-02-06-github-import-machines.mdx │ ├── 2023-03-09-import-all-machines-from-github-repo.mdx │ ├── 2023-04-03-book-a-demo.mdx │ ├── 2023-05-02-track-changes-as-you-work-with-version-history.mdx │ ├── 2023-05-03-introducing-state-new.mdx │ ├── 2023-05-04-everything-new-since-stately-studio-1-0.mdx │ ├── 2023-05-12-office-hours-64.mdx │ ├── 2023-05-16-stately-streams.mdx │ ├── 2023-05-18-explain-your-machines-with-annotations.mdx │ ├── 2023-05-25-announcing-xstate-v5-beta.mdx │ └── 2023-1-27-making-state-machines-global-in-react.mdx ├── jest.config.js ├── lib ├── create-new-post.ts ├── edit-post.ts └── utils.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404.tsx ├── [slug].tsx ├── _app.tsx ├── _document.tsx └── index.tsx ├── patches ├── @jsdevtools+rehype-toc+3.0.2.patch └── prompts+2.4.2.patch ├── public ├── 2019-11-13-no-disabling-a-button-is-not-app-logic.png ├── 2019-12-09-xstate-version-47-and-the-future.png ├── 2020-01-20-redux-is-half-a-pattern-1-2.png ├── 2020-05-22-redux-is-half-a-pattern-2-2.png ├── 2020-05-27-state-management-bad-bolean-good-boolean.png ├── 2020-07-27-state-machines-how-to-stop-making-horcruxes-in-your-code.png ├── 2021-01-11-just-use-props-an-opinionated-guide-to-react-and-xstate.png ├── 2021-01-20-you-dont-need-a-library-for-state-machines.png ├── 2021-04-28-whats-the-difference-between-machine-and-createmachine.png ├── 2021-04-29-should-this-be-a-state-or-in-context.png ├── 2021-04-30-should-this-be-an-action-or-a-service.png ├── 2021-05-13-why-i-love-invoked-callbacks.png ├── 2021-05-27-global-state-xstate-react.png ├── 2021-07-28-usestate-vs-usereducer-vs-xstate-part-1-modals.png ├── 2021-10-02-intro-fsm-sc.png ├── 2021-10-11-convince-teammates.png ├── 2021-12-14-what-are-the-biggest-benefits-youve-had-from-using-state-machines.png ├── 2022-01-27-introducing-typegen.png ├── 2022-02-08-join-the-stately-editor-public-beta.png ├── 2022-02-15-modelling-101-how-to-build-a-statechart-from-scratch.png ├── 2022-03-03-introducing-the-xstate-cli.png ├── 2022-03-15-xstate-vscode-extension-now-available-on-the-open-vsx-registry.png ├── 2022-03-16-introducing-the-new-stately-homepage.png ├── 2022-03-23-stately-changelog-1.png ├── 2022-03-28-stately-is-hiring.png ├── 2022-03-29-introducing-the-roadmap.png ├── 2022-04-05-building-a-video-player.png ├── 2022-05-03-whats-new-may-2022.png ├── 2022-06-07-whats-new-june-2022.png ├── 2022-06-13-nesting-typegen-files.png ├── 2022-06-21-what-is-xstate-used-for.png ├── 2022-07-06-canny.png ├── 2022-07-06-labeled-buttons.png ├── 2022-07-06-search.png ├── 2022-07-06-whats-new-july-2022.png ├── 2022-07-18-just-use-hooks-xstate-in-react-components.png ├── 2022-07-18-useMachine-hook-example.png ├── 2022-08-03-whats-new-august-2022.png ├── 2022-08-10-get-started-with-xstate-using-xsm-snippet.png ├── 2022-08-17-goodbye-use-effect-at-react-next.png ├── 2022-08-22-building-a-resizable-panel.png ├── 2022-08-31-building-a-resizable-panel.png ├── 2022-09-14-whats-new-september-2022.png ├── 2022-10-18-introducing-stately-studio-1-0.png ├── 2022-10-27-studio-tutorials.png ├── 2022-11-22-studio-update-highlighted-selected-event.png ├── 2022-11-22-studio-update-login-providers.png ├── 2022-11-22-studio-update.png ├── 2022-11-29-import-from-code-create-machine.png ├── 2022-11-29-import-from-code-importer.png ├── 2022-11-29-import-from-code-left-drawer.png ├── 2022-11-29-import-from-code-right-panel.png ├── 2022-11-29-import-from-code.png ├── 2022-12-22-machine-recovery.png ├── 2023-01-19-docs-contribute.png ├── 2023-01-19-docs-light-mode.png ├── 2023-01-19-docs-search.png ├── 2023-01-19-docs-xstate.png ├── 2023-01-19-docs.png ├── 2023-01-19-introducing-the-stately-docs.png ├── 2023-01-20-machine-images.png ├── 2023-01-20-read-only.png ├── 2023-01-20-stately-streams.png ├── 2023-01-20-team-in-lisbon.png ├── 2023-01-20-whats-new-in-2023.png ├── 2023-01-20-xstate-vscode.png ├── 2023-02-06-github-import-machines.png ├── 2023-03-09-import-all-machines-from-github-repo.png ├── 2023-04-03-book-a-demo.png ├── 2023-05-02-version-history.png ├── 2023-05-03-introducing-state-new.png ├── 2023-05-04-everything-new-since-stately-studio-1-0.png ├── 2023-05-04-import-from-code.png ├── 2023-05-04-tutorials.png ├── 2023-05-04-version-history.png ├── 2023-05-12-office-hours-64.png ├── 2023-05-16-stately-streams.png ├── 2023-05-18-explain-your-machines-with-annotations.png ├── 2023-05-25-announcing-xstate-v5-beta.png ├── 2023-1-27-making-state-machines-global-in-react.png ├── apple-touch-icon.png ├── august-22-editor-improvements.png ├── changelog-1-events.png ├── context-menu-demo.mp4 ├── favicon.ico ├── fonts │ └── ttcommons.woff2 ├── human-readable-timers.png ├── icon-192.png ├── icon-512.png ├── icon.svg ├── images │ ├── 2023-03-09-import-all-machines-from-github-repo-button.png │ ├── 2023-05-02-example-restore-from-version-list.png │ ├── 2023-05-02-example-version-view-explained.png │ ├── 2023-05-02-example-version-view-plain.png │ ├── 2023-05-02-example-why-audiences.png │ ├── 2023-05-02-example-why-layers-of-detail.png │ ├── 2023-05-02-example-why-modeling-experiments.png │ ├── dark-mode.png │ ├── github-import-machine.gif │ ├── github-install-integration.gif │ ├── github-save-machine.gif │ ├── light-mode.png │ ├── recovery_offline_error.gif │ ├── recovery_restore_machine.gif │ ├── state.new-starter-diagram.png │ ├── stately-editor-public-beta.png │ ├── studio_architecture_challenges.png │ ├── with-typegen.gif │ ├── without-typegen.png │ └── xstate-vscode-settings.png ├── manifest.webmanifest ├── og-image.png ├── select-all-keyboard-shortcut.gif ├── snap-to-elements.mp4 ├── studio-1-0-editor.png ├── studio-1-0-export.png ├── studio-1-0-guards.png ├── studio-1-0-projects.png ├── studio-1-0-simulate.png ├── studio-1-0-teams.png ├── studio-1-0-upgrade.png ├── studio-1-0-visibility.png ├── vercel.svg ├── vscode-darkmode.png ├── vscode-lightmode.png ├── writing-guide-viz-image.png └── xsm-snippet.gif ├── src ├── MetadataContext.tsx ├── PostsContext.tsx ├── Seo.tsx ├── __tests__ │ └── utils.test.ts ├── components │ ├── Announcement.tsx │ ├── Layout.tsx │ ├── Logo.tsx │ ├── MDXComponents.tsx │ ├── PageFooter.tsx │ ├── PageHeader.tsx │ ├── Sidebar.tsx │ └── Viz.tsx ├── feed.tsx ├── posts.ts ├── serializePost.ts ├── theme.ts ├── types.ts └── utils.ts ├── styles ├── globals.scss ├── highlight.css ├── nprogress.css └── post.scss ├── tsconfig.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: [push] 4 | 5 | jobs: 6 | verifying-project: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | 13 | - name: Setup Node 16 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 16.x 17 | 18 | - name: Lint 19 | run: yarn && yarn lint && yarn tsc 20 | 21 | - name: Build 22 | run: yarn next build 23 | env: 24 | CI: "false" 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # cli 37 | lib/*.js 38 | 39 | # feed 40 | public/feeds 41 | .yarn 42 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "[mdx]": { 4 | "editor.wordWrap": "on" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /content/metadata.ts: -------------------------------------------------------------------------------- 1 | import { MetadataOverrides } from "../src/types"; 2 | 3 | export const DEFAULT_TITLE = "Stately Blog"; 4 | export const DEFAULT_DESCRIPTION = "The official blog of Stately.ai"; 5 | export const DEFAULT_URL = "https://stately.ai/blog"; 6 | export const DEFAULT_OG_IMAGE = { 7 | url: "https://stately.ai/blog/og-image.png", // needs to be absolute URL 8 | width: 0, 9 | height: 0, 10 | alt: DEFAULT_TITLE, 11 | type: "image/png", // change based on actual OG image type 12 | }; 13 | 14 | export const AUTHORS = [ 15 | { name: "Farzad Yousefzadeh", twitterHandle: "@farzad_yz" }, 16 | { name: "Laura Kalbag", twitterHandle: "@lauraKalbag" }, 17 | { name: "Matt Pocock", twitterHandle: "@mpocock1" }, 18 | { name: "David K. 🎹", twitterHandle: "@DavidKPiano" }, 19 | { name: "Andarist", twitterHandle: "@AndaristRake" }, 20 | { name: "Jenny Truong", twitterHandle: "@jen_ayy_" }, 21 | { name: "Anders Bech Mellson", twitterHandle: "@andersmellson" }, 22 | ] as const; 23 | 24 | export const makeMetadata = ({ 25 | title = DEFAULT_TITLE, 26 | description = DEFAULT_DESCRIPTION, 27 | url = DEFAULT_URL, 28 | originalURL, 29 | article, 30 | ogImage = DEFAULT_OG_IMAGE.url, 31 | }: MetadataOverrides | undefined = {}) => ({ 32 | title, 33 | description, 34 | authors: AUTHORS, 35 | canonical: originalURL || undefined, 36 | openGraph: { 37 | url, 38 | title, 39 | description, 40 | type: article ? "article" : "website", 41 | locale: "en_US", 42 | site_name: DEFAULT_TITLE, 43 | images: [ 44 | { 45 | url: ogImage, // needs to be absolute URL 46 | width: 0, 47 | height: 0, 48 | alt: `‘${title}’ by ${article?.authors![0]} on the Stately Blog.`, // TODO: multiple authors 49 | type: "image/png", 50 | }, 51 | ], 52 | article, 53 | }, 54 | twitter: { 55 | handle: "@statelyai", 56 | site: "@statelyai", 57 | cardType: "summary_large_image", 58 | creator: article?.authors?.[0], 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /content/posts/2020-05-27-state-management-bad-bolean-good-boolean.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "State Management: How to tell a bad boolean from a good boolean" 3 | description: "TL;DR: Bad booleans represent state. Good booleans are derived from state" 4 | tags: 5 | - tutorial 6 | author: Matt Pocock 7 | category: entry 8 | publishedAt: "2020-05-27" 9 | ogImage: "https://stately.ai/blog/2020-05-27-state-management-bad-bolean-good-boolean.png" 10 | --- 11 | 12 | **TL;DR**: Bad booleans represent state. Good booleans are derived from state. 13 | 14 | When you’re managing state in your app, it’s easy to fall prey to bad booleans. Bad booleans look like this: 15 | 16 | ```js 17 | let isLoading = true; 18 | let isComplete = false; 19 | let hasErrored = false; 20 | ``` 21 | 22 | On the surface, this looks like good code. It appears as though you’ve represented three separate states with proper boolean names. In the ‘model’ you’ve pictured for your state, only one of these states can be true at any one time. 23 | 24 | In a fetch request, you might model the state like this: 25 | 26 | ```js 27 | const makeFetch = async () => { 28 | isLoading = true; 29 | try { 30 | await fetch("/users"); 31 | 32 | isComplete = true; 33 | } catch (e) { 34 | hasErrored = true; 35 | } 36 | isLoading = false; 37 | }; 38 | ``` 39 | 40 | Again, this looks nice. We’re orchestrating our booleans as we move through the async request. 41 | 42 | But there’s a bug here. What happens if we make the fetch, it succeeds, and we make the fetch again? We’ll end up with: 43 | 44 | ```js 45 | let isLoading = true; 46 | let isComplete = true; 47 | let hasErrored = false; 48 | ``` 49 | 50 | ## Implicit states 51 | 52 | You probably hadn’t considered this when you made your initial model. You may have frontend components which are checking for `isComplete === ` true or `isLoading === true`. You might end up with a loading spinner and the previous data showing at the same time. 53 | 54 | How is this possible? Well, you’ve created some implicit states. Let’s imagine you considered 3 states as ones you actually wanted to handle: 55 | 56 | 1. `loading`: Loading the data 57 | 2. `complete`: Showing the data 58 | 3. `errored`: Erroring if the data doesn't turn up 59 | 60 | Well, you’ve actually allowed 8 states! That’s 2 for the first boolean, times 2 for the second, times 2 for the third. 61 | 62 | This is what's known as boolean explosion - I learned about this from [Kyle Shevlin's egghead course](https://egghead.io/lessons/javascript-eliminate-boolean-explosion-by-enumerating-states). 63 | 64 | ## Making states explicit 65 | 66 | How do you get around this? Instead of a system with 8 possible values, we need a system with three possible values. We can do this in Typescript with an enum. 67 | 68 | ```ts 69 | type Status = "loading" | "complete" | "errored"; 70 | 71 | let status: Status = "loading"; 72 | ``` 73 | 74 | We’d implement this in a fetch like this: 75 | 76 | ```ts 77 | const makeFetch = async () => { 78 | status = "loading"; 79 | try { 80 | await fetch("/users"); 81 | 82 | status = "complete"; 83 | } catch (e) { 84 | status = "errored"; 85 | } 86 | }; 87 | ``` 88 | 89 | It’s now impossible to be in the 'loading' and 'complete' state at once - we’ve fixed our bug. We’ve turned our bad booleans into a good enum. 90 | 91 | ## Making good booleans 92 | 93 | But not all booleans are bad. Many popular libraries, such as _react-query_, _apollo_ and _urql_ use booleans in their state. An example implementation: 94 | 95 | ```js 96 | const [result] = useQuery(); 97 | 98 | if (result.isLoading) { 99 | return
Loading...
; 100 | } 101 | ``` 102 | 103 | The reason these are good booleans is that their underlying mechanism is based on an enum. Bad booleans represent state. Good booleans are derived from state: 104 | 105 | ```js 106 | let status: Status = "loading"; 107 | 108 | // Derived from the status above 109 | let isLoading = status === "loading"; 110 | ``` 111 | 112 | You can safely use this `isLoading` to display your loading spinner, happy in the knowledge that you've removed all impossible states. 113 | 114 | ## Addendum: Enums in Javascript 115 | 116 | We can represent a state enum in Javascript as well. While the above code will work without typings, you can represent enums as an object type. 117 | 118 | ```ts 119 | const statusEnum = { 120 | loading: "loading", 121 | complete: "complete", 122 | errored: "errored", 123 | }; 124 | 125 | let status = statusEnum.loading; 126 | 127 | const makeFetch = async () => { 128 | status = statusEnum.loading; 129 | try { 130 | await fetch("/users"); 131 | 132 | status = statusEnum.complete; 133 | } catch (e) { 134 | status = statusEnum.errored; 135 | } 136 | }; 137 | ``` 138 | -------------------------------------------------------------------------------- /content/posts/2021-04-28-whats-the-difference-between-machine-and-createmachine.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: What’s the difference between Machine and createMachine? 3 | description: >- 4 | XState offers two options for declaring machine definitions. This can be 5 | confusing for beginners. Why are there two very similar-looking methods? 6 | What’s the difference? 7 | tags: 8 | - typescript 9 | - state machine 10 | author: Matt Pocock 11 | excerpt: "" 12 | publishedAt: "2021-04-28" 13 | originalURL: "https://dev.to/mpocock1/xstate-what-s-the-difference-between-machine-and-createmachine-15h1" 14 | ogImage: "https://stately.ai/blog/2021-04-28-whats-the-difference-between-machine-and-createmachine.png" 15 | --- 16 | 17 | XState offers two options for declaring machine definitions: 18 | 19 | ```ts 20 | import { Machine } from "xstate"; 21 | 22 | const machine = Machine({ ...config }); 23 | ``` 24 | 25 | …or… 26 | 27 | ```ts 28 | import { createMachine } from "xstate"; 29 | 30 | const machine = createMachine({ ...config }); 31 | ``` 32 | 33 | This can be confusing for beginners. Why are there two very similar-looking methods? What’s the difference? 34 | 35 | ## The Difference 36 | 37 | In Javascript, there is no difference between the two. You can use them completely interchangeably. 38 | 39 | In Typescript, there is only a small difference between them - it’s to do with the ordering of the generics you can pass to the machine. `Machine` allows you to pass a generic called ['Typestates'](https://xstate.js.org/docs/guides/typescript.html#typestates) in the middle of the `Context` and `Event` generics. 40 | 41 | ```ts 42 | import { Machine } from "xstate"; 43 | 44 | interface Context {} 45 | 46 | type Event = { type: "EVENT_NAME" }; 47 | 48 | type States = {}; 49 | 50 | const machine = Machine({ ...config }); 51 | ``` 52 | 53 | Whereas `createMachine` asks you to insert it at the end: 54 | 55 | ```ts 56 | import { createMachine } from "xstate"; 57 | 58 | interface Context {} 59 | 60 | type Event = { type: "EVENT_NAME" }; 61 | 62 | type States = {}; 63 | 64 | const machine = createMachine({ ...config }); 65 | ``` 66 | 67 | Whichever you choose, there is _no functional difference in the created machine_. The two functions reference the same code, and create the machine in the same way. 68 | 69 | ## What should I choose? 70 | 71 | Going forward, you should use `createMachine`. That’s the syntax that will be preferred when v5 releases. But if you're happy with Machine, you can keep using it. 72 | -------------------------------------------------------------------------------- /content/posts/2021-04-29-should-this-be-a-state-or-in-context.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Should this be a state, or in context?" 3 | description: How to decide when to use state or context. 4 | tags: 5 | - context 6 | - state machine 7 | - xstate 8 | - state 9 | author: Matt Pocock 10 | excerpt: "" 11 | publishedAt: "2021-04-29" 12 | ogImage: "https://stately.ai/blog/2021-04-29-should-this-be-a-state-or-in-context.png" 13 | --- 14 | 15 | State machines offer several API’s for expressing state. Like other tools, you can keep arbitrary values in a store (usually expressed as an object) called `context`. 16 | 17 | This is handy for values which change over time and you need to keep updated, like the value of a form input: 18 | 19 | ```ts 20 | import { createMachine, assign } from "xstate"; 21 | 22 | const machine = createMachine({ 23 | context: { 24 | name: "", 25 | }, 26 | on: { 27 | CHANGE_NAME: { 28 | actions: assign((context, event) => { 29 | return { 30 | name: event.value, 31 | }; 32 | }), 33 | }, 34 | }, 35 | }); 36 | ``` 37 | 38 | Every time the `CHANGE_NAME` event is sent to the machine, we'll update the value in `context`. We can then use that value to display the value in our UI or send it to an API. 39 | 40 | XState also gives you another way of expressing state - through finite states. Let's imagine a modal: 41 | 42 | ```ts 43 | const machine = createMachine({ 44 | initial: "closed", 45 | states: { 46 | closed: { 47 | on: { 48 | OPEN: "open", 49 | }, 50 | }, 51 | open: { 52 | on: { 53 | CLOSE: "close", 54 | }, 55 | }, 56 | }, 57 | }); 58 | ``` 59 | 60 | Here, the modal's state is expressed through the `states: {}` attribute, which also defines which events can be received during each state. You can only `CLOSE` the modal when it's `open`, and vice versa. 61 | 62 | ## Which should I choose? 63 | 64 | The choice between using `context` and `states` isn't always clear. For instance, the modal machine above could be expressed using `context`: 65 | 66 | ```ts 67 | const machine = createMachine({ 68 | context: { 69 | isOpen: false, 70 | }, 71 | on: { 72 | OPEN: { 73 | actions: assign({ isOpen: true }), 74 | }, 75 | CLOSE: { 76 | actions: assign({ isOpen: false }), 77 | }, 78 | }, 79 | }); 80 | ``` 81 | 82 | This gives you exactly the same functionality as the states-based one above - you can track when the modal is open and closed, and send the same events. 83 | 84 | The reason this can be expressed using both `states` and `context` is because _all of the events do the same thing no matter what state you’re in_. There are no events you need to declare as impossible in certain states. 85 | 86 | To show you what I mean, let’s imagine a form input inside a modal. We only want to allow changes to the form input while the modal is open. 87 | 88 | ```ts 89 | const machine = createMachine({ 90 | initial: "closed", 91 | context: { 92 | name: "", 93 | }, 94 | states: { 95 | closed: { 96 | on: { 97 | OPEN: "open", 98 | }, 99 | }, 100 | open: { 101 | on: { 102 | CLOSE: "close", 103 | CHANGE_NAME: { 104 | actions: assign((context, event) => { 105 | return { 106 | name: event.value, 107 | }; 108 | }), 109 | }, 110 | }, 111 | }, 112 | }, 113 | }); 114 | ``` 115 | 116 | When the modal is in the `closed` state, the `CHANGE_NAME` event will not change the value in `context`. State machines are great at this - only allowing the things you want to happen to happen. Some other examples might be: 117 | 118 | - Not allowing users to submit a form while the previous API call is loading 119 | - Only allowing users to log in if they’re not already logged in 120 | 121 | ## Putting things in context 122 | 123 | You might be wondering - but, I _can_ express the above in `context`! 124 | 125 | ```ts 126 | const machine = createMachine({ 127 | context: { 128 | name: "", 129 | isOpen: false, 130 | }, 131 | on: { 132 | OPEN: { actions: assign({ isOpen: true }) }, 133 | CLOSE: { actions: assign({ isOpen: false }) }, 134 | CHANGE_NAME: { 135 | actions: assign((context, event) => { 136 | // This acts as the guard to prevent editing 137 | // the name while it's open 138 | if (!context.isOpen) return {}; 139 | return { 140 | name: event.value, 141 | }; 142 | }), 143 | }, 144 | }, 145 | }); 146 | ``` 147 | 148 | I think this is incorrect for two reasons. First, as requirements grow, so will the complexity of your logic. Let’s imagine that the modal can now be either `closing` (i.e. animating out) or `closed`. We’ll soon see an explosion of booleans, as I discussed in [this article on useState/useReducer](https://dev.to/mpocock1/usestate-vs-usereducer-vs-xstate-part-1-modals-569e). 149 | 150 | Second, XState is auto-documenting via the [XState visualiser](https://xstate.js.org/viz/). The more your logic is expressed in `states`, the easier it’s going to be to visualise. The machine above is basically a single state with its logic expressed in ways that XState can’t visualise. 151 | 152 | ## Rules to live by 153 | 154 | You should be keeping most of your state in context. That includes form values, API data - anything which cannot be expressed finitely. 155 | 156 | But state machines are powerful _because_ of their states. Use states when you want to express your logic visually, or gate events to certain states. 157 | -------------------------------------------------------------------------------- /content/posts/2021-12-14-what-are-the-biggest-benefits-youve-had-from-using-state-machines.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: What are the biggest benefits you’ve had from using state machines? 3 | description: >- 4 | At Stately we’re fans of state machines, but we wanted to ask our 5 | community… what are the benefits that you’ve experienced from using state machines? 6 | tags: 7 | - weekly question 8 | - community 9 | - state machine 10 | author: Laura Kalbag 11 | excerpt: "" 12 | publishedAt: "2021-12-14" 13 | ogImage: "https://stately.ai/blog/2021-12-14-what-are-the-biggest-benefits-youve-had-from-using-state-machines.png" 14 | --- 15 | 16 | Last week we asked our community, “What are the biggest benefits you’ve had from using state machines?” 17 | 18 | At Stately, we’re obviously fans of state machines. Still, we wanted to ask our community the benefits they’ve experienced, whether they were working on a specific project or incorporating state machines into their everyday workflow. 19 | 20 | ## Reusability 21 | 22 | Nowadays, so much of tech is component-oriented, so it was fascinating to hear from Martin that he reused the same state machines across different frameworks. 23 | 24 | 25 | 26 | > I think that **the biggest benefit comes from being able to use the same state machine across different frameworks**. With XState, even if you just use React on your projects, you can share your state machine with your [insert framework name here] friends. 27 | 28 | ## Maintaining complex app logic 29 | 30 | 31 | 32 | Pat’s blog post goes into fascinating depth on his work with Crucible. The section on XState starts with Pat’s realization that he needed a state machine and ends with him moving much of their application logic into statecharts. 33 | 34 | > **We didn't start Crucible using XState but by the end of the project it was one of our most valuable tools…** 35 | > 36 | > … 37 | > 38 | > I was still new to statecharts and sort of overwhelmed by the whole idea so I needed to find a small feature I could prototype XState with where it would make sense and add recognizable value to a flow/process that could use more structure to it… 39 | > 40 | > … 41 | > 42 | > I built out the flow based on the services’ state and UI state, I defined all the valid transitions and where they’d go, I wired up the inputs/outputs to it, and then I turned it loose on matchmaking in an actual client and it worked beautifully. 43 | > 44 | > **I could look at the statechart and see the entire flow through the states**, I could see every valid transition that the statechart could take and where it would go, I could paste it into the XState visualizer and actually see a DIAGRAM OF ALL THE STATES AND WALK THROUGH THEM INTERACTIVELY. 45 | 46 | [Read all of Pat’s post on his blog](https://tivac.com/2020/10/19/crucible-rip/). 47 | 48 | ## Maintainability 49 | 50 | Maintainability is a popular feature of state machines, and Joshua is a fan. 51 | 52 | 53 | 54 | Matt’s article on ‘[useState vs useReducer vs XState - Part 1: Modals](/blog/usestate-vs-usereducer-vs-xstate-part-1-modals)’ shares examples of these different approaches and their maintainability. And he followed it up with his [React Finland talk, ‘Make legacy code delightful with statecharts’](https://www.youtube.com/watch?v=zll9uDQOOq0). 55 | 56 | ## Getting to the core of your logic 57 | 58 | The [discussion on our Discord](https://discord.com/channels/795785288994652170/795785288994652174/918546073268670534) focused on mindset and how state machines can help us better understand our app logic. Jan got the conversation started. 59 | 60 | > Certainly for me it must be that **it allows me to very quickly get to the essence and core behaviour of what I'm building**. Playing with boxes and arrows allow me to not get caught up in implementation details while figuring out how I want stuff to work. 61 | > 62 | > Afterwards, implementing actions and services just feels like filling in the blanks. Likewise hooking the machine up with my component is equally trivial: throw some `state.hasTag`, `state.context` and `send` in there, and you’ve got a stew going 63 | 64 | And Steve continued. 65 | 66 | > State machines make complex behaviors tractable; they help you think about the problem. 67 | > 68 | > State machines reveal complexities in ostensibly simple behaviors; they make you think about the problem. 69 | > 70 | > Yin and yang. Good cop, bad cop. **Thinking about your application behavior as a set of state machines focuses your mind on what's really going on inside that tangled mass of behaviors**. 71 | 72 | ## Robustness 73 | 74 | Horacio also explained how the robustness of apps built with state machines could give us more faith in our work. 75 | 76 | > Programming is all about confidence, and for me, **working with state machines gives me the same confidence on my code as writing tests**, with the addition to have a visual representation of my system that I can discuss, show and modify with all my teammates. 77 | > 78 | > Also having the confidence of refactoring code without the fear of breaking other parts of my app!! 79 | > 80 | > Definitely XState is one of my top additions of 2021 to my dev toolbelt! 💪 81 | 82 | That’s just what we like to hear! 83 | 84 | Thank you, everyone, for your responses. We love to hear about how you’re using state machines in your projects. If you’ve got any feedback you want to share with the Stately team, you can [find us on Twitter](https://twitter.com/statelyai) and join the community on [our Discord server](https://discord.gg). 85 | 86 | _Note: quotes have been minimally edited to add emphasis and clarity._ 87 | -------------------------------------------------------------------------------- /content/posts/2022-03-03-introducing-the-xstate-cli.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introducing: The XState CLI" 3 | description: "Get ready to run XState’s typegen commands outside of VSCode in our all-new CLI." 4 | tags: 5 | - cli 6 | - xstate 7 | - announcement 8 | - typescript 9 | author: 10 | - Matt Pocock 11 | excerpt: "Get ready to run XState’s typegen commands outside of VSCode in our all-new CLI." 12 | publishedAt: "2022-03-03" 13 | ogImage: "https://stately.ai/blog/2022-03-03-introducing-the-xstate-cli.png" 14 | --- 15 | 16 | Around a month ago, we released [TypeScript Typegen](/blog/introducing-typescript-typegen-for-xstate) - an enormous upgrade to the TypeScript experience for XState. 17 | 18 | We’ve had a great response to it so far, but it’s only been available for VSCode users. 19 | 20 | Until now. With our new XState CLI, **you can get Typegen from the command line**. 21 | 22 | - Read the full documentation on the [`@xstate/cli` docs](https://xstate.js.org/docs/packages/xstate-cli). 23 | - Check out [our updated TypeScript Typegen guide](https://xstate.js.org/docs/guides/typescript.html#typegen). 24 | - Find the code on the [@xstate/cli GitHub repo](https://github.com/statelyai/xstate-tools/tree/main/apps/cli). 25 | 26 | ## Installation 27 | 28 | ```bash 29 | npm install @xstate/cli 30 | ``` 31 | 32 | OR 33 | 34 | ```bash 35 | yarn add @xstate/cli 36 | ``` 37 | 38 | ## Commands 39 | 40 | ### `xstate typegen ` 41 | 42 | ```bash 43 | xstate typegen "src/**/*.tsx?" 44 | ``` 45 | 46 | Run the typegen against a glob of files. This will scan every targeted file and generate a typegen file accompanying it. It will also import the typegen into your file, as described in [our typegen documentation](https://xstate.js.org/docs/guides/typescript.html#typegen). 47 | 48 | Ensure you wrap your glob in quotes so that it executes correctly. Otherwise, you’ll get unexpected results. 49 | 50 | #### Options 51 | 52 | ```bash 53 | xstate typegen "src/**/*.tsx?" --watch 54 | ``` 55 | 56 | Runs the task on a watch, monitoring for changed files and running the typegen script against them. 57 | 58 | ## The Future 59 | 60 | We’re really excited about the CLI, and all the cool things it’ll enable. The typegen really is just the surface. If you’ve got ideas for what we could do with it, don’t hesitate to add a feature request to the [`xstate-tools` repo](https://github.com/statelyai/xstate-tools/issues). 61 | -------------------------------------------------------------------------------- /content/posts/2022-03-11-xstate-vscode-extension-now-available-on-the-open-vsx-registry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: XState VSCode extension now available on the Open VSX Registry 3 | description: >- 4 | If you use VSCodium, Coder, Gitpod or another editor with VSCode-compatible 5 | extensions, you can now install the XState VSCode extension from the Open VSX 6 | Registry. 7 | tags: 8 | - extension 9 | - open source 10 | - xstate 11 | - vscode 12 | author: 13 | - Laura Kalbag 14 | ogImage: 'https://stately.ai/blog/2022-03-15-xstate-vscode-extension-now-available-on-the-open-vsx-registry.png' 15 | excerpt: 'If you use VSCodium, Coder, Gitpod or another editor with VSCode-compatible 16 | extensions, you can now install the XState VSCode extension from the Open VSX 17 | Registry.' 18 | publishedAt: "2022-03-15" 19 | --- 20 | 21 | If you use VSCodium, Coder, Gitpod or another editor with VSCode-compatible extensions, you can now [install the XState VSCode extension from the Open VSX Registry](https://open-vsx.org/extension/statelyai/stately-vscode). 22 | 23 | We released our [XState VSCode extension](https://marketplace.visualstudio.com/items?itemName=statelyai.stately-vscode) a few weeks ago, which gives you visual editing, autocomplete, typegen and linting for XState. The same extension is now available for VSCodium, Coder and Gitpod, and any other editors with VSCode-compatible extensions through the Open VSX Registry. 24 | 25 | We had many requests to distribute the extension on the Open VSX Registry. As a person who uses [VSCodium](https://vscodium.com) myself, it was exciting to install the extension from inside VSCodium. 26 | 27 | 1. Open the command palette in VSCodium with `shift` + `cmd/ctrl` + `p`. 28 | 2. Search for the **Install Extensions** command and hit enter to open the Extensions search. 29 | 3. Search for **XState** to find the XState VSCode extension and install the extension using the **Install** button. 30 | 4. Once you have installed the extension, you can find the extension commands by searching for **XState** in the command palette. 31 | 32 | You can also [download the XState VSCode extension directly from the Open VSX Registry](https://open-vsx.org/extension/statelyai/stately-vscode). 33 | 34 | Please give us your feedback and let us know if you encounter any issues in [our Discord](https://discord.gg/xstate) or [our xstate-tools repository](https://github.com/statelyai/xstate-tools/issues). -------------------------------------------------------------------------------- /content/posts/2022-03-15-introducing-the-new-stately-homepage.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introducing the new Stately homepage 3 | description: "Last week we launched the new Stately homepage, which we hope will make it easy to understand what Stately and XState are and help you convince your team to use state machines." 4 | tags: 5 | - stately 6 | - homepage 7 | - introduction 8 | author: 9 | - Laura Kalbag 10 | excerpt: "" 11 | publishedAt: "2022-03-16" 12 | ogImage: "https://stately.ai/blog/2022-03-16-introducing-the-new-stately-homepage.png" 13 | --- 14 | 15 | Last week we launched [the new Stately homepage](https://stately.ai), which we hope will make it easy to understand what Stately and XState are and help you convince your team to use state machines. 16 | 17 | You can watch us talk about the new design and its implementation during [last week’s office hours](https://youtu.be/WoPCd4D--Gk?t=771). I’m particularly impressed by David’s SVG arrows! 18 | 19 | And if you have any feedback about the homepage, please let us know on [our Discord](https://discord.gg/xstate). We always appreciate your thoughts. 20 | -------------------------------------------------------------------------------- /content/posts/2022-03-23-stately-changelog-1.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Stately Changelog #1 - Snap To Elements" 3 | description: The Stately Editor changelog is where we discuss new features released to the Stately Editor beta. 4 | tags: 5 | - stately 6 | - announcement 7 | - editor 8 | - beta 9 | - changelog 10 | author: 11 | - Matt Pocock 12 | originalURL: "" 13 | excerpt: "" 14 | publishedAt: "2022-03-23" 15 | ogImage: "https://stately.ai/blog/2022-03-23-stately-changelog-1.png" 16 | --- 17 | 18 | Happy Wednesday! Time for our first Editor Changelog blog, where we’ll talk about the new updates we’ve shipped in the editor. 19 | 20 | ## Snap To Elements 21 | 22 | When you’re placing states and events in the canvas, you’ll now be able to snap them to surrounding elements. 23 | 24 | Snapping is a hugely requested feature and should help make your charts neater and more legible. 25 | 26 |