21
loading...
This website collects cookies to deliver better user experience
const countingMachineDefinition = {
initial: "counting",
context: { count: 0 },
states: {
counting: {
on: {
INCREMENT: {
actions: assign({
count: (context) => context.count + 1,
}),
},
},
},
},
};
initial
state that the machine will be in when it is first turned on.context
that the machine will start with in its initial state. This is the secondary state, all the data beyond the current state itself.states
, at least one, that the machine can be in. In this case I just have the counting
state.on
which it will respond to with a transition and actions. In this case I just have the INCREMENT
event. When this event is triggered in the counting
state, it will transition to itself and an assign
action will update the count
in the context
.target
will implicitly do a self transition. In the state diagram, rather than the an arrow going from this state to another state, the arrow points to itself. This means that when that state receives that event, it will transition right back to itself. A transition always takes place.target
wasn't specified at all for counting
's INCREMENT
event, the self transition will be an internal transition (as opposed to an external transition). This means that on this internal transition, we don't leave the current state node. The implications of that are that the entry
and exit
actions of that state will not be triggered.internal
option as true
.states: {
counting: {
on: {
INCREMENT: {
internal: true,
actions: assign({
count: (context) => context.count + 1,
}),
},
},
},
},
target
is undefined
.states: {
counting: {
on: {
INCREMENT: {
target: undefined,
actions: assign({
count: (context) => context.count + 1,
}),
},
},
},
},
states: {
counting: {
on: {
INCREMENT: {
target: "counting",
actions: assign({
count: (context) => context.count + 1,
}),
},
},
entry: () => {
console.log("Entering 'counting'");
},
exit: () => {
console.log("Exiting 'counting'");
},
},
},
target
option which points to the parent state, counting
. To be sure that this brings back the entry
and exit
actions, I've add a couple logging actions. On each button click, we'll see the exit
and then immediately the entry
actions be triggered.The task is to build a frame containing a label or read-only textfield T and a button B. Initially, the value in T is “0” and each click of B increases the value in T by one.
Counter serves as a gentle introduction to the basics of the language, paradigm and toolkit for one of the simplest GUI applications imaginable. Thus, Counter reveals the required scaffolding and how the very basic features work together to build a GUI application. A good solution will have almost no scaffolding.
$ yarn add xstate @xstate/react
createMachine
function which we will import.import { createMachine } from "xstate";
useMachine
hook.import { useMachine } from '@xstate/react';
machine.js
file.import { createMachine } from "xstate";
const countingMachineDefinition = {
initial: "counting",
context: { count: 0 },
states: {
counting: {
on: {
INCREMENT: {
actions: 'incrementCount',
},
},
},
},
};
export const countingMachine = createMachine(countingMachineDefinition);
initial
, context
, and states
.initial
specifies the state that this machine should start in when it is first interpreted. Our starting state is counting
. That's also our only state.context
is where we define an object containing any initial context for our machine. The only piece of context we are keeping track of is count
. We will have it start at 0
.states
lists the finite set of states that make up this state machine. At any given time, our machine is going to be in one of these defined states. This is an extremely simple state machine that has a single state—counting
.states
definition.states: {
counting: {
on: {
INCREMENT: {
actions: 'incrementCount',
},
},
},
},
counting
state contains some information about itself. It tells us what events it responds to in the on
object. Since we are only counting up, the counting
state will only respond to the INCREMENT
event.INCREMENT
event is sent, the incrementCount
action will be triggered. You might have noticed that there is no function definition for incrementCount
.INCREMENT
event, we would see the following warning in the console.Warning: No implementation found for action type 'incrementCount'
'incrementCount'
string with an inline function or we can define a function under that name in an actions
section.import { createMachine, assign } from "xstate";
const countingMachineDefinition = {
initial: "counting",
context: { count: 0 },
states: {
counting: {
on: {
INCREMENT: {
actions: assign({
count: (context) => context.count + 1,
}),
},
},
},
},
};
export const countingMachine = createMachine(countingMachineDefinition);
assign
from xstate
. It is being used to generate an action handler that will update the machine's context. The only context that needs updating is count
. Similar to React, Redux, and other state management libraries, the context value is updated using a function that provides the current context and returns the updated context value.INCREMENT
event, it will trigger this assign({ ... })
action that increments the count. Each subsequent event, will be working with the newest version of the context
which will contain the incremented count.import React from "react";
import { useMachine } from "@xstate/react";
import { countingMachine } from "../../src/machines/counter";
const Task1 = () => {
const [state, send] = useMachine(countingMachine);
return (
<>
<p>Count: {state.context.count}</p>
<button onClick={() => send('INCREMENT')}>
Increment
</button>
</>
);
INCREMENT
event will be sent to the machine. The count
context will be incremented and that value will trickle down to being rendered into the view via {state.context.count}
.