xstate

xstate: type: final: go back to initial/idle state


I have an xstate state machine with 3 states. When data is fetched and final state isreached, the state machine is 'Done'. I would like to go back to initial state of idle after 'finally completing the task'. How do I accomplish that? For example,

states: {
idle: {
  id: 'initialState'
},
waitingForA: {
  invoke: { /*Promise*/ },
  onDone: { target: 'wiatingForB' },
  onError: { alert(); }
},
waitingForB: {
      invoke: { /*Promise*/ },
      onDone: {
        target: 'waitingForC',
        actions: assign({ bReturnCode: (context, event) => event.data, })
      }
    },
    waitingForC: {
      invoke: { /*Promise*/ },
      onDone: {
        target: 'success',
        actions: assign({ cReturnCode: (context, event) => event.data, })
      },
      onError{
           target: 'showAlert'
      }
    },
    success: {
      type: 'final' //here, I would like ot go back to idle state;
    },
    final: {target: 'idle'}
  }

}

Solution

  • You can use an always transition which transitions to the idle state without the need to explicitly fireing an event. Just keep in mind that the success state is not allowed to be a final state otherwise the execution of the service would stop.

    You can read more about these automatic ('eventless') transitions in the XState docs here.

      success: {
        // is not allowed to be a final state
        // because you still want to transition to idle
        // and the execution of the service would stop
        always: 'idle',
      },
    

    Full machine definition

        id: 'some-machine'
        predictableActionArguments: true,
        context: { bReturnCode: null },
        initial: 'idle',
        states: {
          idle: {
            id: 'initialState',
            on: {
              start: 'waitingForB'
            }
          },
          waitingForA: { /* ... */ },
          showAlert: { /* ... */ },
          waitingForB: {
            invoke: {
              "id": "getStuffC",
              "src": (context, event) => debugFetch,
              onDone: {
                target: 'waitingForC',
                actions: assign({ bReturnCode: (context, event) => event.data })
              },
              onError: {
                target: 'showAlert'
              }
            },
          },
          waitingForC: {
            invoke: {
              "id": "getStuffC",
              "src": (context, event) => debugFetch,
              onDone: {
                target: 'success',
                actions: assign({ cReturnCode: (context, event) => event.data })
              },
              onError: {
                target: 'showAlert'
              }
            },
          },
          success: {
            // is not allowed to be a final state
            // because you still want to transition to idle
            // and the execution of the service would stop
            always: 'idle',
          },
        }
    
    function debugFetch() {
      return new Promise((resolve, reject) => {
        const delayInMs = 1000
        setTimeout(() => {
          if (Math.random() <= 0.01) {
            reject('some error')
          }
          resolve('success')
        }, delayInMs)
      })
    }