xState

2024.10.17

Finite state machine with xState

#JavaScript#tool
use client /components/post/reExport , date: , tags: [ ], imgUrl: , desc: , body: ( <> <H>Why</H> <ul> <li>If we need to add some feature it is intuitive to introduce some a boolean flag</li> <li> programming is bad</li> <li>Processes in the app better to be based on named states</li> <li>As a programmer we have to code the transitions between states</li> <li>xState helps with this</li> </ul> <H>xState</H> <ul> <li>XState is a state management and orchestration solution for JavaScript apps</li> <li> XState allows to manage workflow state by creating a model logic as actors and state machines </li> <li>Can be used in the frontend, backend, or wherever JavaScript runs</li> <li> <Code>npm i xstate @xstate/react</Code> install with npm </li> <Code block jsx>{ ; const countMachine = createMachine({ context: { count: 0, }, on: { INC: { actions: assign({ count: ({ context }) => context.count + 1, }), }, DEC: { actions: assign({ count: ({ context }) => context.count - 1, }), }, SET: { actions: assign({ count: ({ event }) => event.value, }), }, }, }); const countActor = createActor(countMachine).start(); countActor.subscribe((state) => { console.log(state.context.count); }); countActor.send({ type: }); // logs 1 countActor.send({ type: }); // logs 0 countActor.send({ type: , value: 10 }); // logs 10 const textMachine = createMachine({ context: { committedValue: , value: }, initial: , states: { reading: { on: { : { target: } } }, editing: { on: { : { actions: assign({ value: ({ event }) => event.value }) }, : { actions: assign({ committedValue: ({ context }) => context.value }), target: }, : { actions: assign({ value: ({ context }) => context.committedValue }), target: } } } } }) const textActor = createActor(textMachine).start() textActor.subscribe((state) => { console.log(state.context.value) }) textActor.send({ type: }) // logs textActor.send({ type: }) // logs textActor.send({ type: }) // logs textActor.send({ type: }) // logs textActor.send({ type: Hello world Hello world text.cancel }</Code> </ul> <H>Actor</H> <ul> <li> <i>Actor</i> is a{ } <Lnk path= >mathematical model</Lnk> for building message-based systems by using actors to communicate </li> <li>When you run a state machine in XState, it becomes an actor</li> <li>Actors can communicate with each other via asynchronous message passing - events</li> <li>Actors process one message at a time</li> <li>Actor has its own internal state that can only be updated by the actor itself</li> <li>Actor may update its internal state in response to a message it receives</li> <li>Actors can create new actors</li> <li>Actors can be created and destroyed as needed to handle the workload efficiently</li> </ul> <Code block jsx>{ ; const toggleMachine = createMachine({ id: , initial: , states: { Inactive: { on: { toggle: }, }, Active: { on: { toggle: }, }, }, }); // Create an actor that you can send events to. // Note: the actor is not started yet! const actor = createActor(toggleMachine); // Subscribe to snapshots (emitted state changes) from the actor actor.subscribe((snapshot) => { console.log( , snapshot.value); }); // Start the actor actor.start(); // logs // Send events actor.send({ type: }); // logs actor.send({ type: }); // logs }</Code> <H>Main principles</H> <ul> <li> <Lnk path= > https://stately.ai/docs/state-machines-and-statecharts </Lnk> </li> <li> A <i>state machine</i> is used to describe the behavior of something </li> <li> The machine describes the thing s status or mode, which could be as simple as <i>Asleep</i>{ } and <i>Awake</i> </li> <li>A state machine can only be in one state at a time</li> <li> When a state machine starts, it enters the <i>initial state</i> first. </li> <li> <i>Events</i> cause <i>transitions</i> </li> <li>When an event happens, the machine transitions to the next state</li> <li> The dog goes between <code>asleep</code> and <code>awake</code> through the{ } <code>wake up</code> and <code>fall asleep</code> events. </li> <Code block jsx>{ ; const dogMachine = createMachine({ id: , initial: , states: { asleep: { on: { , } }, awake: { on: { , } }, //... } }); s a <i>Finite State Machine</i> (FSM) because it has a finite number of states </li> <li> Events and transitions are defined inside the <code>on</code> property of a state. </li> <li> Transition has a <i>source</i> state which comes before the transition, and a{ } <i>target</i> state, which comes after the transition </li> <li> Most processes with states will have a <i>final</i> state, the last state when the process is finished </li> </ul> <H>Parent state</H> <ul> <li> A <i>parent</i> state is a state that can contain more states, also known as <i>child</i>{ } states. </li> <li>Child states can only happen when the parent state is happening</li> <li> An <i>atomic</i> state is a state that doesn export const walkMachine = createMachine({ id: , initial: , states: { waiting: { on: { : { target: , reenter: true, }, }, }, : { initial: , on: { : { target: , reenter: true, }, }, states: { walking: { on: { , }, }, running: { on: { , }, }, }, }, : { type: , }, }, }) } <i>regions</i>, are active simultaneously </li> </ul> <Code block jsx>{ call machine , states: { mic: { initial: , states: { muted: { on: { unmute: , }, }, unmuted: { on: { mute: , }, }, }, }, video: { initial: , states: { : { on: { hide: , }, }, : { on: { show: , }, }, }, }, }, }) import { createMachine, assign } from ; const machine = createMachine({ context: { count: 0 }, on: { someEvent: { // No target actions: assign({ count: ({context}) => context.count + 1, }) } } }); import { assign, createMachine } from ; export const toggleMachine = createMachine({ id: , context: { count: 0 }, initial: , states: { Inactive: { on: { toggle: }, }, Active: { entry: assign({ count: ({ context }) => context.count + 1 }), on: { toggle: }, after: { 2000: }, }, }, }); import { assign, createMachine } from ; export const toggleMachine = createMachine({ id: , context: ({ input }) => ({ count: 0, maxCount: input.maxCount }), initial: , states: { Inactive: { on: { toggle: { // Only trigger toggle transition if count is less than maxCount guard: ({ context }) => context.count < context.maxCount, target: } } }, Active: { entry: assign({ count: ({ context }) => context.count + 1 }), on: { toggle: }, after: { 2000: }, }, }, }); const actor = createActor(toggleMachine, { input: { maxCount: 10 } }); actor.subscribe(snapshot => { console.log( , snapshot.value); }); actor.start(); actor.send({ type: }); import { assign, createMachine } from ; import { useMachine } from ; export const toggleMachine = createMachine({ id: , context: { count: 0 }, initial: , states: { Inactive: { on: { toggle: }, }, Active: { entry: assign({ count: ({ context }) => context.count + 1 }), on: { toggle: }, after: { 2000: }, }, }, }); const App = () => { const [state, send] = useMachine(toggleMachine); return ( <div> <div>Value: {state.value}</div> <button onClick={() => send({ type: })}> Toggle </button> </div> ); };