Guards

Many times, you'll want a transition between states to only take place if certain conditions on the state (finite or extended) or the event are met. For instance, let's say you're creating a machine for a search form, and you only want search to be allowed if:

  • the user is allowed to search (.canSearch in this example)
  • the search event query is not empty.

This is a good use case for a "transition guard", which determines if a transition can occur given the state and the event. A transition with guards is called a conditional transition.

Guard Functions

A guard is a function that takes 2 arguments:

and returns either true or false, which signifies whether the transition should be allowed to take place.

Guards are specified on the .cond property of a transition, as a string or guard object with a { type: '...' } property:















 
 













 
 
 
 
 



import { Machine } from 'xstate';

const searchMachine = Machine(
  {
    id: 'search',
    initial: 'idle',
    context: {
      canSearch: true
    },
    states: {
      idle: {
        on: {
          SEARCH: {
            target: 'searching',
            // Only transition to 'searching' if the guard (cond) evaluates to true
            cond: 'searchValid' // or { type: 'searchValid' }
          }
        }
      },
      searching: {
        entry: 'executeSearch'
        // ...
      },
      searchError: {
        // ...
      }
    }
  },
  {
    guards: {
      searchValid: (context, event) => {
        return context.canSearch && event.query && event.query.length > 0;
      }
    }
  }
);

If the cond guard returns false, then the transition will not be selected, and no transition will take place from that state node.

Example of usage with context:

import { interpret } from 'xstate';

const searchService = interpret(searchMachine)
  .onTransition(state => console.log(state.value))
  .start();

searchService.send({ type: 'SEARCH', query: '' });
// => 'idle'

searchService.send({ type: 'SEARCH', query: 'something' });
// => 'searching'

Guard implementations can be quickly prototyped by specifying the guard cond function directly in the machine config:




 



// ...
SEARCH: {
  target: 'searching',
  cond: (context, event) => context.canSearch && event.query && event.query.length > 0
}
// ...

It is not recommended to keep the machine config like this in production code, as this makes it difficult to debug, serialize, test, and accurately visualize actions. Always prefer refactoring inline guard implementations in the guards property of the machine options, like the previous example.

Serializing Guards

Guards can (and should) be serialized as a string or an object with the { type: '...' } property. The implementation details of the guard are specified on the guards property of the machine options, where the key is the guard type (specified as a string or object) and the value is a function that takes three arguments:

  • context - the current machine context
  • event - the event that triggered the (potential) transition
  • guardMeta 4.4+ - an object containing meta data about the guard and transition, including:
    • cond - the original cond object
    • state - the current machine state, before transition

Refactoring the above example:









 
 
 







 
 
 
 
 



const searchMachine = Machine(
  {
    // ...
    states: {
      idle: {
        on: {
          SEARCH: {
            target: 'searching',
            // The 'searchValid' guard implementation details are
            // specified in the machine config
            cond: 'searchValid' // or { type: 'searchValid' }
          }
        }
      }
      // ...
    }
  },
  {
    guards: {
      searchValid: (context, event) => {
        return context.canSearch && event.query && event.query.length > 0;
      }
    }
  }
);

Custom Guards 4.4+

Sometimes, it is preferable to not only serialize state transitions in JSON, but guard logic as well. This is where serializing guards as objects is helpful, as objects may contain relevant data:









 
 
 
 
 







 
 
 
 
 
 
 
 
 
 



const searchMachine = Machine(
  {
    // ...
    states: {
      idle: {
        on: {
          SEARCH: {
            target: 'searching',
            // Custom guard object
            cond: {
              type: 'searchValid',
              minQueryLength: 3
            }
          }
        }
      }
      // ...
    }
  },
  {
    guards: {
      searchValid: (context, event, { cond }) => {
        // cond === { type: 'searchValid', minQueryLength: 3 }
        return (
          context.canSearch &&
          event.query &&
          event.query.length > cond.minQueryLength
        );
      }
    }
  }
);

Multiple Guards

If you want to have a single event transition to different states in certain situations you can supply an array of conditional transitions. Each transition will be tested in order, and the first transition whose cond guard evaluates to true will be taken.

For example, you can model a door that listens for an OPEN event, goes to the 'opened' state if you are an admin, or goes to the 'closed.error' state if alert-ing is true, or goes to the 'closed.idle' state otherwise.

























 
 
 


































import { Machine, actions, interpret, assign } from 'xstate';

const doorMachine = Machine(
  {
    id: 'door',
    initial: 'closed',
    context: {
      level: 'admin',
      alert: false // alert when intrusions happen
    },
    states: {
      closed: {
        initial: 'idle',
        states: {
          idle: {},
          error: {}
        },
        on: {
          SET_ADMIN: {
            actions: assign({ level: 'admin' })
          },
          OPEN: [
            // Transitions are tested one at a time.
            // The first valid transition will be taken.
            { target: 'opened', cond: 'isAdmin' },
            { target: '.error', cond: 'shouldAlert' },
            { target: '.idle' }
          ]
        }
      },
      opened: {
        on: {
          CLOSE: 'closed'
        }
      }
    }
  },
  {
    guards: {
      isAdmin: context => context.level === 'admin',
      shouldAlert: context => context.alert === true
    }
  }
);

const doorService = interpret(doorMachine)
  .onTransition(state => console.log(state.value))
  .start();
// => { closed: 'idle' }

doorService.send('OPEN');
// => { closed: 'error' }

doorService.send('SET_ADMIN');
// => { closed: 'error' }
// (state does not change, but context changes)

doorService.send('OPEN');
// => 'opened'
// (since context.isAdmin === true)

The cond function must always be a pure function that only references the context and event arguments.

Do not overuse guard conditions. If something can be represented discretely as two or more separate events instead of multiple conds on a single event, it is preferable to avoid cond and use multiple types of events instead.

"In State" Guards

The in property takes a state ID as an argument and returns true if and only if that state node is active in the current state. For example, we can add a guard to the traffic light machine:
























 







const lightMachine = Machine({
  id: 'light',
  initial: 'green',
  states: {
    green: { on: { TIMER: 'yellow' } },
    yellow: { on: { TIMER: 'red' } },
    red: {
      initial: 'walk',
      states: {
        walk: {
          /* ... */
        },
        wait: {
          /* ... */
        },
        stop: {
          /* ... */
        }
      },
      on: {
        TIMER: [
          {
            target: 'green',
            in: '#light.red.stop'
          }
        ]
      }
    }
  }
});

When an in-state guard is present with other cond guards in the same transition, all guards must evaluate to true for the transition to be taken.

Using "in state" guards is usually a sign that the machine can be refactored in a way that makes their usage unnecessary. Prefer avoiding "in state" guards when possible.

SCXML

The cond property is equivalent to the cond attribute on the <transition> element:

{
  on: {
    e: {
      target: 'foo',
      cond: context => context.x === 1
    }
  }
}
<transition event="e" cond="x == 1" target="foo" />

Likewise, the in property is equivalent to the In() predicate:

{
  on: {
    e: {
      target: 'cooking',
      in: '#closed'
    }
  }
}
<transition cond="In('closed')" target="cooking"/>