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>
);
};