From d540213a57332a346b5df09b898de1a1f82b7ebb Mon Sep 17 00:00:00 2001 From: Bruno Cangussu Date: Thu, 31 Oct 2024 10:54:11 -0300 Subject: [PATCH] Updating for 0.6.0 release --- CHANGELOG.md | 9 +++ README.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 188 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02acd65..fcb0a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log + +## [0.6.0] - 2024-08-31 + +### Added + +- Added two new atoms, `atomWithActor` and `atomWithActorSnapshot` to conform with XState v5's actor model +- Refactored `atomWithMachine` to be simpler and consume the two other methods +- Added tests for the two new methods + ## [Unreleased] ## [0.5.0] - 2024-06-20 diff --git a/README.md b/README.md index 97daa27..59ca951 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,181 @@ Jotai integration library for XState https://jotai.org/docs/integrations/xstate + +## Usage + +### `atomWithActor` + +Creates an atom that creates, stores and manages an Actor, given it's logic. Follows mostly the same API as [`createActor`](https://www.jsdocs.io/package/xstate#createActor). + +```tsx +const promiseLogic = fromPromise(() => fetch('http://some.host/...')) // or fromTransition, fromObservable, fromEventObservable, fromCallback, createMachine +const actorAtom = atomWithActor(promiseLogic) + +const Component = () => { + const [actorRef, send] = useAtom(actorAtom) + ... +} +``` + +Can also be called with a second `opts` argument for setting up actor parameters. In typescript it's important to correctly type the actors Input, Output and Events. Refer to the [examples](examples/01_typescript/src/app.tsx) for a full implementation + +```tsx +const promiseLogic = fromPromise(({ input }: { input: { id: number } }) => + fetch(`http://some.host/${input.id}`), +); + +const actorAtom = atomWithActor(promiseLogic, { input: { id: 2 } }); +// ^ Will type-error if you don't provide input +``` + +Either param can also be a Getter function, allowing you to derive data from other atoms + +````tsx +const promiseLogicAtom = atom(fromPromise(({ input }: { input: { id: number } }) => + fetch(`http://some.host/${input.id}`), +)); + +const idAtom = atom(2) + +const actorAtom = atomWithActor((get) => get(promiseLogicAtom), (get) => { + return { input: { id: get(idAtom) } } +}); + +You can fully type all inputs, outputs and events. + +```tsx +type PromiseLogicOutput = string; +type PromiseLogicInput = { duration: number }; +type PromiseLogicEvents = +| { type: 'elapsed'; value: number } +| { type: 'completed' }; + +const promiseLogicAtom = atom( + fromPromise( + async ({ emit, input }) => { + const start = Date.now(); + let now = Date.now(); + do { + await new Promise((res) => setTimeout(res, 200)); + emit({ type: 'elapsed', value: now - start }); + now = Date.now(); + } while (now - start < input.duration); + emit({ type: 'completed' }); + return 'Promise finished'; + }, + ), +); + +const actorAtom = atomWithActor((get) => get(promiseLogicAtom), { + input: { duration: 3000 }, +}); + +const Component = () => { + // actorRef allows access to the return of 'createActor' + const [actorRef, send] = useAtom(actorAtom); + + useEffect(() => { + const subscription = actorRef.on('elapsed', console.log); + return () => subscription.unsubscribe; + }, [actorRef]); + + return ... +}; +```` + +**Important!!** +By default `atomWithActor` will call `actor.start()` as soon as it mounts. To change this behaviour you can provide `{ autoStart: false }` in your options and start it manually + +```tsx +const promiseLogic = fromPromise( + () => new Promise((res) => setTimeout(res, 1000)), +); // or fromTransition, fromObservable, fromEventObservable, fromCallback, createMachine +const actorAtom = atomWithActor(promiseLogic, { autoStart: false }); + +const Component = () => { + const [actorRef, send] = useAtom(actorAtom); + return ( + + ); +}; +``` + +### `atomWithActorSnapshot` + +Provides access to an actors up-to-date [`snapshot`](https://www.jsdocs.io/package/xstate#Actor.getSnapshot) while also handling it's lifecycle and listeners. Takes in an instanced actor or a getter function that returns one. + +```tsx +type PromiseLogicOutput = string +// or fromTransition, fromObservable, fromEventObservable, fromCallback, createMachine +const promiseLogic = fromPromise(() => new Promise( + res => setTimeout(() => res("Return from inside logic"), 1000) +)) + +const actorAtom = atomWithActor(promiseLogic) +// Here get() is required because the actor logic is also stored in an atom +const actorSnapshot = atomWithActorSnapshot(get => get(actorAtom)) + +const Component = () => { + const [actorRef, send] = useAtom(actorAtom) + const [snapshot, clear] = useAtom(actorSnapshot) + return ( +
+ {snapshot.state === "active" && "Waiting on timeout"} + {snapshot.state === "done" && `Timeout done! Actor output is: ${snapshot.output}`} +
+ ) + ... +} +``` + +Calling this atom's `write` function (named `clear` in the example above) will clear the internal snapshot and reset the listeners. This is usefull when combined with calling `send(RESTART)` on the actor logic, especially when it depends on derived values. + +```tsx +type PromiseLogicOutput = string +type PromiseLogicInput = { duration: number } +// or fromTransition, fromObservable, fromEventObservable, fromCallback, createMachine +const promiseLogic = fromPromise(({input}) => new Promise( + res => setTimeout(() => res("Return from inside logic"), input.duration) +)) + +const durationAtom = atom(1000) + +const actorAtom = atomWithActor(promiseLogic) +// Here get() is required because the actor logic is also stored in an atom +const actorSnapshot = atomWithActorSnapshot(get => get(actorAtom)) + +const Component = () => { + const [actorRef, send] = useAtom(actorAtom) + const [snapshot, clear] = useAtom(actorSnapshot) + const [duration, setDuration] = useAtom(durationAtom) + return ( +
+ {snapshot.state === "active" && "Waiting on timeout"} + {snapshot.state === "done" && ( +
+ Waited for {duration}ms +
+ Actor output is: {snapshot.output} + +
+ )} +
+ ) + ... +} +``` + +### `atomWithMachine` + +https://jotai.org/docs/integrations/xstate diff --git a/package.json b/package.json index 528b133..5900946 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jotai-xstate", "description": "👻🤖", - "version": "0.5.0", + "version": "0.6.0", "type": "module", "author": "Daishi Kato", "repository": {