Search code examples
typescriptxstate

Method referenced in XState machine is not callable within class but works when declared as global


Why cannot I reference a method declared within the same class as XState service? If I move the method outside of the class, it works fine.

export class LoaderMachine {
    service = interpret(createMachine<LoaderContext, LoaderEvent, LoaderState>(
        {
        .....
        }, {
            actions: {
                fetch() {
                    this.fetchDataInsideClass(); // not callable
                    fetchData(); // global declaration works
                },
                restart() {
                    console.log('Recovering from error')
                    service.send('RESTART');
                },
                finished(context, event) {
                    console.log('Done');
                }
            }
        }));

    async fetchDataInsideClass() {
        try {
            console.log('Started');
            service.send('SUCCESS');
        } catch (err) {
            console.error(`Failed with ${err}`);
            service.send('ERROR');
        }
    }
}

async function fetchData() {
    try {
        console.log('Started');
        service.send('SUCCESS');
    } catch (err) {
        console.error(`Failed with ${err}`);
        service.send('ERROR');
    }
}

Results in a compilation error:

TS2349: This expression is not callable.
Not all constituents of type 'AssignActionObject<LoaderContext, LoaderEvent> | ActionObject<LoaderContext, LoaderEvent> | ActionFunction<...>' are callable.     
Type 'AssignActionObject<LoaderContext, LoaderEvent>' has no call signatures

Solution

  • The problem in your sample code is that actions.fetch is a function declaration that will set its execution context (the this binding) in the runtime depending on how it's called. For more information, check out the MDN reference.

    A quick fix is to declare actions.fetch (and all the other action implementations) as an arrow function that will be bound to the instance of LoaderMachine class.

    actions: {
        fetch: () => {
          this.fetchDataInsideClass(); // not callable
          fetchData(); // global declaration works
        },
        restart: () => {
          console.log("Recovering from error");
          service.send("RESTART");
        },
        finished: (context, event) => {
          console.log("Done");
        },
      },