What is a state?
A state represents a specific mode or condition that your application can be in at any given time. In XState, a machine can only be in one state at a time (unless using parallel states).
Defining states
States are defined in the states property of a machine configuration:
import { createMachine } from 'xstate' ;
const machine = createMachine ({
id: 'door' ,
initial: 'closed' ,
states: {
closed: {
on: {
OPEN: 'opened'
}
},
opened: {
on: {
CLOSE: 'closed'
}
}
}
});
Initial state
Every state machine must have an initial state, specified with the initial property:
const machine = createMachine ({
initial: 'idle' , // Required
states: {
idle: {},
loading: {},
success: {},
error: {}
}
});
Forgetting to specify an initial state will cause an error.
State values
The current state is represented by a state value, which can be:
Simple state value
A string representing the current state:
const snapshot = actor . getSnapshot ();
console . log ( snapshot . value ); // 'idle'
Nested state value
An object for hierarchical states:
const machine = createMachine ({
initial: 'loading' ,
states: {
loading: {
initial: 'pending' ,
states: {
pending: {},
complete: {}
}
},
loaded: {}
}
});
// State value: { loading: 'pending' }
Parallel state value
An object with multiple active states:
const machine = createMachine ({
type: 'parallel' ,
states: {
mode: {
initial: 'light' ,
states: { light: {}, dark: {} }
},
size: {
initial: 'medium' ,
states: { small: {}, medium: {}, large: {} }
}
}
});
// State value: { mode: 'light', size: 'medium' }
State node properties
Entry actions
Actions executed when entering a state:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {},
loading: {
entry : () => console . log ( 'Started loading' )
}
}
});
Exit actions
Actions executed when leaving a state:
const machine = createMachine ({
initial: 'idle' ,
states: {
loading: {
entry : () => console . log ( 'Started loading' ),
exit : () => console . log ( 'Stopped loading' )
},
success: {}
}
});
Description
A description of what the state represents:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
description: 'Waiting for user input'
},
loading: {
description: 'Fetching data from API'
}
}
});
Tags for categorizing states:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
tags: [ 'visible' ]
},
loading: {
tags: [ 'visible' , 'pending' ]
},
error: {
tags: [ 'visible' , 'failed' ]
}
}
});
const snapshot = actor . getSnapshot ();
if ( snapshot . hasTag ( 'pending' )) {
console . log ( 'Operation in progress' );
}
Final states
Final states indicate that a state machine has completed:
const machine = createMachine ({
initial: 'active' ,
states: {
active: {
on: {
COMPLETE: 'done'
}
},
done: {
type: 'final'
}
}
});
const actor = createActor ( machine );
actor . start ();
console . log ( actor . getSnapshot (). status ); // 'active'
actor . send ({ type: 'COMPLETE' });
console . log ( actor . getSnapshot (). status ); // 'done'
When a state machine reaches a final state, it automatically stops and cannot receive more events.
Checking states
Using matches()
Check if the current state matches a value:
const snapshot = actor . getSnapshot ();
if ( snapshot . matches ( 'loading' )) {
console . log ( 'Currently loading' );
}
// For nested states
if ( snapshot . matches ({ loading: 'pending' })) {
console . log ( 'Loading is pending' );
}
Using hasTag()
Check if the current state has a tag:
if ( snapshot . hasTag ( 'pending' )) {
// Show loading spinner
}
Using state value
Directly access the state value:
const value = snapshot . value ;
switch ( value ) {
case 'idle' :
console . log ( 'Ready' );
break ;
case 'loading' :
console . log ( 'Loading...' );
break ;
case 'success' :
console . log ( 'Success!' );
break ;
}
Attach metadata to states:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
meta: {
message: 'Waiting for input'
}
},
loading: {
meta: {
message: 'Loading data' ,
progress: 0
}
}
}
});
const snapshot = actor . getSnapshot ();
const meta = snapshot . getMeta ();
console . log ( meta [ 'idle' ]?. message ); // 'Waiting for input'
State transitions
Define how states transition in response to events:
const machine = createMachine ({
initial: 'idle' ,
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
on: {
SUCCESS: 'success' ,
ERROR: 'error'
}
},
success: {
on: {
RETRY: 'loading'
}
},
error: {
on: {
RETRY: 'loading'
}
}
}
});
Best practices
Use descriptive state names that clearly indicate what the system is doing, like loadingUserData instead of loading.
Keep states at the same level of abstraction. Don’t mix high-level states like authenticated with low-level states like buttonPressed.
Use tags to categorize states that share common behaviors or UI requirements.
Next steps
Transitions Learn about state transitions
Context Add data to your state machines
Hierarchical states Organize states hierarchically
Parallel states Run multiple states simultaneously