An Introduction to XState in TypeScript

Neeraj Khosla

February 4, 2021


When systems and programs are small, state management is usually rather simple, and it’s easy to envision the status of the application and the various ways in which it can change over time. It’s when we scale and our applications become more complex that challenges arise. As systems grow larger, it’s vital to not just have a plan for state management, but a vision for how the entire system functions. This is where state machines come into play and can offer a comprehensive solution to state management by helping us model our application state.

State machines allow us to build structured and robust UIs while forcing us, as developers, to think through each and every state our application could be in. This added insight can enhance communication not only among developers, but also between developers, designers, and product managers as well.

What are statecharts and state machines?

A finite state machine is a mathematical system that can only ever be in one of a finite number of defined states. A traffic light is a simple example. A traffic light only has four states that it could ever be in: one for each of its three lights (red, yellow, and green) being on while the other two lights are off. The fourth is an error state where the traffic light has malfunctioned.

Statecharts are used to map out the various states of a finite system, similar to a basic user flow chart. Once the finite number of states are determined, transitions — the set of events that move us between each state — are defined. The basic combination of states and transitions are what make up the machine. As the application grows, new states and transitions can be added with ease. The process of configuring the state machine forces us to think through each possible application state, thus clarifying the application’s design.

XState is a library developed by David Khourshid that provides us with the ability to create and run state machines in JavaScript/TypeScript, along with a thorough and easily navigated set of documentation. It also provides us with the XState visualizer, which allows both technical and non-technical people to see how we can move through the finite set of states for a given system, thus providing “a common language for designers and developers.”

Using TypeScript — Context, Schema, and Transitions

We can also type our XState machine using TypeScript. XState works nicely with TypeScript because XState makes us think through our various application states ahead of time, allowing us to clearly define our types as well.

XState Machine instances take two object arguments, configuration and options. The configuration object is the overall structure of the states and transitions. The options object allows us to further customize our machine, and will be explained in depth below.

The three type arguments that we use to compose our machine are schematransitions, and context. They help us describe every possible state, map out how we move from state to state, and define all the data that can be stored as we progress through the machine. All three are fully defined before the machine is initialized:

  • Schema is an entire overview of the map of the machine. It defines all of the states that the application could be in at any given moment.
  • Transitions are what allow us to move from state to state. They can be triggered in the UI by event handlers. Instead of the event handlers containing stateful logic, they simply send the type of the transition along with any relevant data to the machine, which will then transition to the next state according to the schema.
  • Context is a data store that is passed into your state machine. Similar to Redux, context represents all the data potentially needed at any point in the lifecycle of your program as it moves from state to state. This means that while we may not have all the actual data upon initialization, we do need to define the shape and structure of our context data store ahead of time.

Let’s take some time to look at the initial configuration of a state machine:

  • ID is a string that refers to this specific machine.
  • Initial refers to the initial state of the machine.
  • Context is an object that defines the initial state and shape of our context data store, similar to initial state in Redux. Here, we set out all the potential pieces of state data as the keys in this object. We provide initial values where appropriate, and unknown or possibly absent values can be declared here as undefined.

Our machine has all the information it needs to initialize, we have mapped out the various states of the machine, and the gears of our machine are moving. Now let’s dive into how to utilize the various tools provided by XState to trigger transitions and handle data.


To illustrate how XState helps us manage application state, we’ll build a simple example state machine for an email application. Let’s think of a basic email application where, from our initial HOME_PAGE state (or welcome screen), we can transition into an INBOX state (the screen where we read our emails). We can define our schema with these two states and define a transition called OPEN_EMAILS.

With our two states and transition defined, it is clear to see how our state machine begins in the HOME_PAGE state and has its transition defined in the on property.


1. Services + Actions

We now have a state machine with a basic transition, but we haven’t stored any data in our context. Once a user triggers the OPEN_EMAILS transition, we will want to invoke a service to fetch all the emails for the user and use the assign action to store them into our context. Both of these are defined in the options object. And we can define emails within our context as an optional array since upon initialization of the machine we haven’t yet fetched any emails. We will have to add two new states to our schema: a LOADING_EMAILS pending state and an APPLICATION_ERROR error state, if this request fails. We can invoke this request to fetch the emails in our new LOADING_EMAILS state.

The four keys in the configuration for invoke are idsrconDone, and onError, with the id being an identifier for the invocation. The src is the function fetchEmails that returns our promise containing the email data. Upon a successful fetch, we will move into onDone, where we can use the assign action to store the email data returned from our fetch in our context using the setEmails action. As you can see, the two arguments to fetchEmails are context and event, giving it access to all the context and event values. We also have to let our machine know where to go next by providing a target state, which in this instance is our INBOX. We have a similar structure for a failed fetch, in which our target is an error state, APPLICATION_ERROR, that returns to the HOME_PAGE state after five seconds.

2. Guards

Conditional state changes can be handled by the use of guards, which are defined in the options object. Guards are functions that, once evaluated, return a boolean. In XState, we can define this guard in our transition with the key cond.

Let’s add another state for drafting an email, DRAFT_EMAIL. If a user was previously drafting an email when the application successfully fetches email data, the application would take the user back to the DRAFT_EMAIL page instead of the INBOX. We’ll implement this conditional logic with an isDraftingEmail function. If the user was in the process of drafting an email when data was successfully fetched, isDraftingEmail will return true and send the machine back to the DRAFT_EMAIL state; if it returns false, it will send the user to the INBOX state. Our guard will be handled in a new state called ENTERING_APPLICATION that will be responsible for checking this condition. By using the always key when defining this state, we tell XState to execute this conditional logic immediately upon entry of the state.

XState Visualizer

One of XState’s best features is the XState visualizer, which takes in our machine configuration and automatically provides an interactive visual representation of our state machine. These visualizations are how “state machines provide a common language for designers and developers.”

A final look at our XState visualizer shows us the map of our entire email application. Use either link below to test out our machine in a new tab! Once the sandbox is loaded in a new tab, it should open a second new tab with the visualizer. If you don’t see the visualizer, disable your pop-up blocker and refresh the sandbox.

In the visualizer, click on the OPEN_EMAILS transition to run the state machine. To change the outcome of the machine, comment/uncomment the return values in the fetchEmails and isDraftingEmails functions in the Sandbox.

XState Email Application Visualizer


XState provides a high level understanding of our application via its schema and visualizer, while still offering more granular visibility and control of state and data through its configuration. Its usability helps us tame complexity as our application grows, making it an excellent choice for any developer. Thank you so much for reading and keep an eye out for part II: XState and React!

About the author

Neeraj Khosla

Giant Machines Team


Stay in the loop

Keep up to date with our newest products and all the latest
in technology and design.
Keep up to date with our newest products
and all the latest
in technology and design.

Other blog posts

Helping to Change the Face of Fintech

Giant Machines leads FinTech Focus – a program designed for rising first year college students who have an interest in finance, computer science, and technology.

Enablement, Upskilling, and the Meaning of Learning

The foundation of Giant Machines is in learning, education, and growth, whether that's for software engineers or developing other helpful skills. Here's how it helps us—and our clients.

How We Celebrated Our Company Culture with Giant Machines Week

Giant Machines week celebrates our company's community and culture with presentations, workshops, and a yacht ride around the Hudson River.


Learn more about us here at Giant Machines and how you can work with us.

What we do

We leverage best-in-class talent to create leading edge digital solutions.


Know your next move


Develop beautiful products


Enrich your tech knowledge

Our work

Learn more about our partnerships and collaborations.

Our perspective

Stay up to date with the latest in technology and design.