Understanding State Machines
Explains how state machines work in the dotLottie React Native Player. Covers the core model, workflow, controller methods, setting inputs, event callbacks, and interactive examples.
Understanding State Machines
This page explains how state machines work in the React Native player — what they are, how they integrate with animation playback, and when to use them. It also documents the controller methods for loading, starting, and interacting with state machines.
Note: State machine support is an actively developing feature. APIs and capabilities might change. If your state machine isn't working as expected please create an issue on Github↗.
Core Workflow
Define: Create your state machine logic (states, transitions, events, context) within your
.lottiefile.Load Animation: Set up the
DotLottiecomponent with your animation source.Load State Machine: Specify the
stateMachineIdprop or load programmatically via the controller ref.Start State Machine: The state machine starts automatically when using
stateMachineId, or callstateMachineStart()after programmatic loading.Interact: Send inputs and fire events from your application code using the controller ref.
Listen (Optional): Use event callback props to react to state changes, transitions, and input changes.
Stop (Optional): Stop the state machine using the controller when interactivity is no longer needed.
Basic Setup
The simplest way to use a state machine is to set the stateMachineId prop and attach a ref to access the controller:
import React, { useRef, useState } from 'react';
import { View, Text } from 'react-native';
import { DotLottie, type Dotlottie } from '@lottiefiles/dotlottie-react-native';
function StateMachineExample() {
const controllerRef = useRef<Dotlottie>(null);
const [currentState, setCurrentState] = useState('idle');
return (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<DotLottie
ref={controllerRef}
source={{ uri: 'https://lottie.host/your-animation.lottie' }}
style={{ width: 300, height: 300 }}
stateMachineId="myStateMachine"
onStateMachineStateEntered={(state) => {
setCurrentState(state);
}}
/>
<Text>Current State: {currentState}</Text>
</View>
);
}Setting Inputs
State machines can use boolean, numeric, and string inputs to control transitions. Set inputs via the controller ref.
Boolean Inputs
controllerRef.current?.stateMachineSetBooleanInput('isActive', true);
controllerRef.current?.stateMachineSetBooleanInput('isActive', false);Numeric Inputs
controllerRef.current?.stateMachineSetNumericInput('speed', 5.0);String Inputs
controllerRef.current?.stateMachineSetStringInput('theme', 'dark');Interactions and Events
Pointer and click interactions defined in the state machine's interactions array (e.g., Click, PointerDown, PointerEnter) are handled automatically by the React Native player based on user touches on the DotLottie component. You do not need to wire up gesture handlers for these.
Use stateMachineFire to trigger Event-type inputs explicitly from your application code — for example, responding to a button press or other app-level gesture outside the animation component:
Firing Events
Events cause immediate transitions in the state machine:
// Fire a named event
controllerRef.current?.stateMachineFire('onTap');Controller State Machine Methods
The following methods are available on the controller ref for state machine interaction:
| Method | Description |
stateMachineLoad(stateMachineId: string) | Loads a state machine by ID. |
stateMachineStart() | Starts the loaded state machine. |
stateMachineStop() | Stops the state machine. |
stateMachineFire(event: string) | Fires a named event to the state machine. |
stateMachineSetBooleanInput(key: string, value: boolean) | Sets a boolean input. |
stateMachineSetNumericInput(key: string, value: number) | Sets a numeric input. |
stateMachineSetStringInput(key: string, value: string) | Sets a string input. |
State Machine Event Callbacks
Listen to state machine events using callback props on the DotLottie component:
<DotLottie
source={require('./animation.lottie')}
style={{ width: 300, height: 300 }}
stateMachineId="myStateMachine"
// Lifecycle
onStateMachineStart={() => {
console.log('State machine started');
}}
onStateMachineStop={() => {
console.log('State machine stopped');
}}
// State changes
onStateMachineStateEntered={(state) => {
console.log('Entered state:', state);
}}
onStateMachineStateExit={(state) => {
console.log('Exited state:', state);
}}
onStateMachineTransition={(from, to) => {
console.log(`Transition: ${from} → ${to}`);
}}
// Input changes
onStateMachineBooleanInputChange={(name, oldVal, newVal) => {
console.log(`${name}: ${oldVal} → ${newVal}`);
}}
onStateMachineNumericInputChange={(name, oldVal, newVal) => {
console.log(`${name}: ${oldVal} → ${newVal}`);
}}
onStateMachineStringInputChange={(name, oldVal, newVal) => {
console.log(`${name}: ${oldVal} → ${newVal}`);
}}
// Events and errors
onStateMachineInputFired={(inputName) => {
console.log('Input fired:', inputName);
}}
onStateMachineError={(error) => {
console.log('Error:', error);
}}
onStateMachineCustomEvent={(event) => {
console.log('Custom event:', event);
}}
/>Complete Interactive Example
Here's a complete example of an interactive button with multiple states:
import React, { useRef, useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { DotLottie, type Dotlottie } from '@lottiefiles/dotlottie-react-native';
function InteractiveButton() {
const controllerRef = useRef<Dotlottie>(null);
const [currentState, setCurrentState] = useState('idle');
const [isEnabled, setIsEnabled] = useState(true);
const handlePressIn = () => {
if (isEnabled) {
controllerRef.current?.stateMachineFire('pressDown');
}
};
const handlePressOut = () => {
if (isEnabled) {
controllerRef.current?.stateMachineFire('pressUp');
}
};
const toggleEnabled = () => {
const newValue = !isEnabled;
setIsEnabled(newValue);
controllerRef.current?.stateMachineSetBooleanInput('isEnabled', newValue);
};
const handleReset = () => {
controllerRef.current?.stateMachineFire('reset');
};
return (
<View style={styles.container}>
<TouchableOpacity
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={1}
>
<DotLottie
ref={controllerRef}
source={require('./button-animation.lottie')}
style={{ width: 200, height: 200 }}
stateMachineId="buttonStateMachine"
onStateMachineStateEntered={(state) => {
setCurrentState(state);
}}
/>
</TouchableOpacity>
<Text style={styles.stateText}>State: {currentState}</Text>
<View style={styles.controls}>
<TouchableOpacity style={styles.button} onPress={toggleEnabled}>
<Text>{isEnabled ? 'Disable' : 'Enable'}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={handleReset}>
<Text>Reset</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
stateText: { fontSize: 18, marginVertical: 20 },
controls: { flexDirection: 'row', gap: 10 },
button: { paddingHorizontal: 20, paddingVertical: 10, backgroundColor: '#e0e0e0', borderRadius: 8 },
});Loading State Machines Programmatically
You can load state machines programmatically instead of using the stateMachineId prop:
// Load by ID after animation is ready
controllerRef.current?.stateMachineLoad('myStateMachine');
controllerRef.current?.stateMachineStart();
// Stop when done
controllerRef.current?.stateMachineStop();Important: Ensure the animation is loaded before calling stateMachineLoad. You can use the onLoad callback to confirm the animation is ready.
Understanding State Machine Components (Defined in .lottie)
Understanding the structure defined within the .lottie file helps in using the player API effectively:
Descriptor: Contains the state machine's unique
id(used instateMachineLoador thestateMachineIdprop) and the index of theinitialstate.States: Defines possible animation states. The main type
PlaybackStatecontrols animation aspects likemarker,segment,loop,autoplay,speed, andmode.Transitions: Rules for moving between states (
from_state,to_state) based on a trigger event.Events: Triggers for transitions, fired via the
stateMachineFiremethod or by setting inputs.Guards (Optional): Conditions within transitions that check context variables to allow or prevent the transition.
Context Variables (Optional): Stored data (
Numeric,String,Boolean) used by guards.
Debugging and Troubleshooting
State Machine Not Loading/Starting:
Check ID: Verify the
stateMachineIdmatches the ID in the.lottiemanifest.Animation Loaded? Ensure the animation loaded before programmatic loading. Use the
onLoadcallback to confirm.
Events Not Triggering Transitions:
Check Event Name: Ensure you're firing the correct event name that matches the state machine definition.
Verify Guards: Check guard conditions defined in the
.lottiefile.State Machine Started? Confirm the state machine is running.
Animation Playback Issues in States:
Check Marker/Segment: Ensure
markernames orsegmentframe numbers defined in states exist in the animation.
Debugging Tip: Use
onStateMachineStateEntered,onStateMachineStateExit, andonStateMachineTransitioncallbacks to log state changes.