diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1255f32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +_env.ts + +# Testing +coverage + +# Build Outputs +.next/ +out/ +build +dist + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem \ No newline at end of file diff --git a/fetch-on-mount-with-xstate/package.json b/fetch-on-mount-with-xstate/package.json new file mode 100644 index 0000000..e7466b3 --- /dev/null +++ b/fetch-on-mount-with-xstate/package.json @@ -0,0 +1,16 @@ +{ + "author": "Sandro Maglione ", + "license": "MIT", + "devDependencies": { + "@types/node": "^22.8.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.6.3" + }, + "dependencies": { + "@xstate/react": "^4.1.3", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "xstate": "^5.18.2" + } +} diff --git a/fetch-on-mount-with-xstate/pnpm-lock.yaml b/fetch-on-mount-with-xstate/pnpm-lock.yaml new file mode 100644 index 0000000..c9b8f61 --- /dev/null +++ b/fetch-on-mount-with-xstate/pnpm-lock.yaml @@ -0,0 +1,170 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xstate/react': + specifier: ^4.1.3 + version: 4.1.3(@types/react@18.3.12)(react@18.3.1)(xstate@5.18.2) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + xstate: + specifier: ^5.18.2 + version: 5.18.2 + devDependencies: + '@types/node': + specifier: ^22.8.6 + version: 22.8.6 + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + +packages: + + '@types/node@22.8.6': + resolution: {integrity: sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + + '@xstate/react@4.1.3': + resolution: {integrity: sha512-zhE+ZfrcCR87bu71Rkh5Z5ruZBivR/7uD/dkelzJqjQdI45IZc9DqTI8lL4Cg5+VN2p5k86KxDsusqW1kW11Tg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + xstate: ^5.18.2 + peerDependenciesMeta: + xstate: + optional: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + use-isomorphic-layout-effect@1.1.2: + resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + xstate@5.18.2: + resolution: {integrity: sha512-hab5VOe29D0agy8/7dH1lGw+7kilRQyXwpaChoMu4fe6rDP+nsHYhDYKfS2O4iXE7myA98TW6qMEudj/8NXEkA==} + +snapshots: + + '@types/node@22.8.6': + dependencies: + undici-types: 6.19.8 + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.1': + dependencies: + '@types/react': 18.3.12 + + '@types/react@18.3.12': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@xstate/react@4.1.3(@types/react@18.3.12)(react@18.3.1)(xstate@5.18.2)': + dependencies: + react: 18.3.1 + use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.12)(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + optionalDependencies: + xstate: 5.18.2 + transitivePeerDependencies: + - '@types/react' + + csstype@3.1.3: {} + + js-tokens@4.0.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + typescript@5.6.3: {} + + undici-types@6.19.8: {} + + use-isomorphic-layout-effect@1.1.2(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + + xstate@5.18.2: {} diff --git a/fetch-on-mount-with-xstate/src/machine.tsx b/fetch-on-mount-with-xstate/src/machine.tsx new file mode 100644 index 0000000..ff37542 --- /dev/null +++ b/fetch-on-mount-with-xstate/src/machine.tsx @@ -0,0 +1,88 @@ +import { useMachine } from "@xstate/react"; +import { assign, fromPromise, setup } from "xstate"; + +type Input = Readonly<{ id: number }>; +type Post = Readonly<{ + userId: number; + id: number; + title: string; + body: string; +}>; + +const machine = setup({ + types: { + // 👇 Context contains possible `error` and `post` + context: {} as { error: unknown | null; post: Post | null }, + input: {} as Input, + + // 👇 Initial default event (https://stately.ai/docs/input#initial-event-input) + events: {} as Readonly<{ type: "xstate.init"; input: Input }>, + }, + actors: { + /// 👇 Fetch request inside an actor + fetch: fromPromise(({ input }: { input: Input }) => + fetch(`https://jsonplaceholder.typicode.com/posts/${input.id}`).then( + async (response) => (await response.json()) as Post + ) + ), + }, + actions: { + onUpdateError: assign((_, { error }: { error: unknown }) => ({ error })), + onUpdatePost: assign((_, { post }: { post: Post }) => ({ post })), + }, +}).createMachine({ + context: { error: null, post: null }, + initial: "Loading", + // 👇 Execute the actor at the start ("on mount") + invoke: { + src: "fetch", + // 👇 Input from component + input: ({ event }) => { + if (event.type === "xstate.init") { + return event.input; + } + + throw new Error("Missing machine input"); + }, + onError: { + target: ".Error", + actions: { + type: "onUpdateError", + // 👇 When error response set `error` in context + params: ({ event }) => ({ error: event.error }), + }, + }, + onDone: { + target: ".Success", + actions: { + type: "onUpdatePost", + // 👇 When successful response set `user` in context + params: ({ event }) => ({ post: event.output }), + }, + }, + }, + states: { + Loading: {}, + Error: {}, + Success: {}, + }, +}); + +export default function Page() { + const [snapshot] = useMachine(machine, { + input: { id: 1 }, + }); + + // 👇 Match on states instead of brittle multiple `useState` + return ( +
+ {snapshot.matches("Loading") && Loading...} + {snapshot.matches("Error") && ( +
{JSON.stringify(snapshot.context.error, null, 2)}
+ )} + {snapshot.matches("Success") && ( +
{JSON.stringify(snapshot.context.post, null, 2)}
+ )} +
+ ); +} diff --git a/fetch-on-mount-with-xstate/tsconfig.json b/fetch-on-mount-with-xstate/tsconfig.json new file mode 100644 index 0000000..0d6ebb5 --- /dev/null +++ b/fetch-on-mount-with-xstate/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "module": "preserve", + "noEmit": true, + "lib": ["es2022", "DOM"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/upload-file-xstate-machine-with-effect/package.json b/upload-file-xstate-machine-with-effect/package.json new file mode 100644 index 0000000..7b2313e --- /dev/null +++ b/upload-file-xstate-machine-with-effect/package.json @@ -0,0 +1,17 @@ +{ + "author": "Sandro Maglione ", + "license": "MIT", + "devDependencies": { + "@types/node": "^22.8.6", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "typescript": "^5.6.3" + }, + "dependencies": { + "@xstate/react": "^4.1.3", + "effect": "^3.10.8", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "xstate": "^5.18.2" + } +} diff --git a/upload-file-xstate-machine-with-effect/pnpm-lock.yaml b/upload-file-xstate-machine-with-effect/pnpm-lock.yaml new file mode 100644 index 0000000..471ca09 --- /dev/null +++ b/upload-file-xstate-machine-with-effect/pnpm-lock.yaml @@ -0,0 +1,193 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xstate/react': + specifier: ^4.1.3 + version: 4.1.3(@types/react@18.3.12)(react@18.3.1)(xstate@5.18.2) + effect: + specifier: ^3.10.8 + version: 3.10.8 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + xstate: + specifier: ^5.18.2 + version: 5.18.2 + devDependencies: + '@types/node': + specifier: ^22.8.6 + version: 22.8.6 + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + typescript: + specifier: ^5.6.3 + version: 5.6.3 + +packages: + + '@types/node@22.8.6': + resolution: {integrity: sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==} + + '@types/prop-types@15.7.13': + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + + '@types/react@18.3.12': + resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + + '@xstate/react@4.1.3': + resolution: {integrity: sha512-zhE+ZfrcCR87bu71Rkh5Z5ruZBivR/7uD/dkelzJqjQdI45IZc9DqTI8lL4Cg5+VN2p5k86KxDsusqW1kW11Tg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + xstate: ^5.18.2 + peerDependenciesMeta: + xstate: + optional: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + effect@3.10.8: + resolution: {integrity: sha512-vCKKp7EL6j7x1xfBbmndcQvooNE5FEDxkVro2yqEGBR2Ogn4I4XVK5A4eLVlDxS9egvX7EFaBwDRfm7k6IM6oA==} + + fast-check@3.22.0: + resolution: {integrity: sha512-8HKz3qXqnHYp/VCNn2qfjHdAdcI8zcSqOyX64GOMukp7SL2bfzfeDKjSd+UyECtejccaZv3LcvZTm9YDD22iCQ==} + engines: {node: '>=8.0.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + use-isomorphic-layout-effect@1.1.2: + resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.2.2: + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + xstate@5.18.2: + resolution: {integrity: sha512-hab5VOe29D0agy8/7dH1lGw+7kilRQyXwpaChoMu4fe6rDP+nsHYhDYKfS2O4iXE7myA98TW6qMEudj/8NXEkA==} + +snapshots: + + '@types/node@22.8.6': + dependencies: + undici-types: 6.19.8 + + '@types/prop-types@15.7.13': {} + + '@types/react-dom@18.3.1': + dependencies: + '@types/react': 18.3.12 + + '@types/react@18.3.12': + dependencies: + '@types/prop-types': 15.7.13 + csstype: 3.1.3 + + '@xstate/react@4.1.3(@types/react@18.3.12)(react@18.3.1)(xstate@5.18.2)': + dependencies: + react: 18.3.1 + use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.12)(react@18.3.1) + use-sync-external-store: 1.2.2(react@18.3.1) + optionalDependencies: + xstate: 5.18.2 + transitivePeerDependencies: + - '@types/react' + + csstype@3.1.3: {} + + effect@3.10.8: + dependencies: + fast-check: 3.22.0 + + fast-check@3.22.0: + dependencies: + pure-rand: 6.1.0 + + js-tokens@4.0.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + pure-rand@6.1.0: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + typescript@5.6.3: {} + + undici-types@6.19.8: {} + + use-isomorphic-layout-effect@1.1.2(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + + use-sync-external-store@1.2.2(react@18.3.1): + dependencies: + react: 18.3.1 + + xstate@5.18.2: {} diff --git a/upload-file-xstate-machine-with-effect/src/HtmlChangeEvent.ts b/upload-file-xstate-machine-with-effect/src/HtmlChangeEvent.ts new file mode 100644 index 0000000..2244297 --- /dev/null +++ b/upload-file-xstate-machine-with-effect/src/HtmlChangeEvent.ts @@ -0,0 +1,18 @@ +import { Data, Effect, Layer } from "effect"; +import type * as React from "react"; + +class MissingFileError extends Data.TaggedError("MissingFileError")<{}> {} + +const make = (event: React.ChangeEvent) => ({ + getRequiredFile: Effect.fromNullable(event.target.files?.item(0)).pipe( + Effect.mapError(() => new MissingFileError()) + ), +}); + +export class HtmlChangeEvent extends Effect.Tag("HtmlChangeEvent")< + HtmlChangeEvent, + Readonly> +>() { + static readonly fromEvent = (event: React.ChangeEvent) => + Layer.succeed(this, make(event)); +} diff --git a/upload-file-xstate-machine-with-effect/src/UploadImageForm.tsx b/upload-file-xstate-machine-with-effect/src/UploadImageForm.tsx new file mode 100644 index 0000000..81d84b7 --- /dev/null +++ b/upload-file-xstate-machine-with-effect/src/UploadImageForm.tsx @@ -0,0 +1,14 @@ +import { machine } from "./upload-file-machine"; + +import { useMachine } from "@xstate/react"; + +export default function UploadImageForm() { + const [snapshot, send] = useMachine(machine); + return ( + send({ type: "upload-file", event })} + /> + ); +} diff --git a/upload-file-xstate-machine-with-effect/src/upload-file-machine.ts b/upload-file-xstate-machine-with-effect/src/upload-file-machine.ts new file mode 100644 index 0000000..440555b --- /dev/null +++ b/upload-file-xstate-machine-with-effect/src/upload-file-machine.ts @@ -0,0 +1,65 @@ +import { HtmlChangeEvent } from "./HtmlChangeEvent"; + +import { Effect } from "effect"; +import { assign, fromPromise, setup } from "xstate"; + +export const machine = setup({ + types: { + context: {} as { + file: globalThis.File | null; + submitError: unknown | null; + }, + events: {} as Readonly<{ + type: "upload-file"; + event: React.ChangeEvent; + }>, + }, + actors: { + uploadFile: fromPromise< + globalThis.File, + { event: React.ChangeEvent } + >(({ input: { event } }) => + Effect.runPromise( + HtmlChangeEvent.getRequiredFile.pipe( + Effect.provide(HtmlChangeEvent.fromEvent(event)) + ) + ) + ), + }, +}).createMachine({ + id: "upload-file-machine", + context: { file: null, submitError: null }, + initial: "Idle", + states: { + Idle: { + on: { + "upload-file": { + target: "Uploading", + }, + }, + }, + Uploading: { + invoke: { + src: "uploadFile", + input: ({ event }) => ({ event: event.event }), + onError: { + target: "Idle", + actions: assign(({ event }) => ({ + submitError: event.error, + })), + }, + onDone: { + target: "Uploaded", + actions: assign(({ event }) => ({ file: event.output })), + }, + }, + }, + Uploaded: { + on: { + "upload-file": { + target: "Uploading", + }, + }, + }, + }, +}); diff --git a/upload-file-xstate-machine-with-effect/tsconfig.json b/upload-file-xstate-machine-with-effect/tsconfig.json new file mode 100644 index 0000000..0d6ebb5 --- /dev/null +++ b/upload-file-xstate-machine-with-effect/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "module": "preserve", + "noEmit": true, + "lib": ["es2022", "DOM"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/xstate-empty-machine-template/package.json b/xstate-empty-machine-template/package.json new file mode 100644 index 0000000..2170ca3 --- /dev/null +++ b/xstate-empty-machine-template/package.json @@ -0,0 +1,10 @@ +{ + "author": "Sandro Maglione ", + "license": "MIT", + "devDependencies": { + "typescript": "^5.6.3" + }, + "dependencies": { + "xstate": "^5.18.2" + } +} diff --git a/xstate-empty-machine-template/pnpm-lock.yaml b/xstate-empty-machine-template/pnpm-lock.yaml new file mode 100644 index 0000000..cf894be --- /dev/null +++ b/xstate-empty-machine-template/pnpm-lock.yaml @@ -0,0 +1,33 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + xstate: + specifier: ^5.18.2 + version: 5.18.2 + devDependencies: + typescript: + specifier: ^5.6.3 + version: 5.6.3 + +packages: + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + xstate@5.18.2: + resolution: {integrity: sha512-hab5VOe29D0agy8/7dH1lGw+7kilRQyXwpaChoMu4fe6rDP+nsHYhDYKfS2O4iXE7myA98TW6qMEudj/8NXEkA==} + +snapshots: + + typescript@5.6.3: {} + + xstate@5.18.2: {} diff --git a/xstate-empty-machine-template/src/machine.ts b/xstate-empty-machine-template/src/machine.ts new file mode 100644 index 0000000..f2fca46 --- /dev/null +++ b/xstate-empty-machine-template/src/machine.ts @@ -0,0 +1,19 @@ +import { setup } from "xstate"; + +export const machine = setup({ + types: { + context: {} as {}, + events: {} as Readonly<{ type: "event" }>, + input: {} as {}, + children: {} as {}, + }, + actions: {}, + actors: {}, +}).createMachine({ + id: "", + context: {}, + initial: "Idle", + states: { + Idle: {}, + }, +}); diff --git a/xstate-empty-machine-template/tsconfig.json b/xstate-empty-machine-template/tsconfig.json new file mode 100644 index 0000000..611482c --- /dev/null +++ b/xstate-empty-machine-template/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "module": "preserve", + "noEmit": true, + "lib": ["es2022"] + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}