Xstate: TypeScript error-messages incomprehensible on attempt to invoke a service

Created on 8 Feb 2020  路  12Comments  路  Source: davidkpiano/xstate

Description
I'm trying to use this documentation to wrap my head around integrating promise-based operations into a Xstate-machine while using TypeScript. I would like to pass on some properties of the original send-event. So that those, together with the results of a promise-based operation, are ending up in the context. See Reproduction further down for details.

Expected Result
Documentation that explains what to keep in mind typing a service invocation in a real-world scenario and/or some words on how to design certain scenarios, in case there's something wrong with the way I'm trying to solve this problem.

Actual Result

(property) StateNodeConfig<MachineContext, any, EventObject>.states?: StatesConfig<MachineContext, any, EventObject> | undefined
The mapping of state node keys to their state node configurations (recursive).

No overload matches this call.
  Overload 1 of 2, '(config: MachineConfig<MachineContext, any, AnyEventObject>, options?: Partial<MachineOptions<MachineContext, AnyEventObject>> | undefined, initialContext?: MachineContext | undefined): StateMachine<...>', gave the following error.
    Type '{ idle: { on: { FETCH: string; }; }; updateUser: { invoke: { id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<...>; onDone: { ...; }; onError: { ...; }; }; }; success: {}; failure: { ...; }; }' is not assignable to type 'StatesConfig<MachineContext, any, AnyEventObject>'.
      Property 'updateUser' is incompatible with index signature.
        Type '{ invoke: { id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }; }' is not assignable to type 'StateNodeConfig<MachineContext, any, AnyEventObject>'.
          Types of property 'invoke' are incompatible.
            Type '{ id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }' is not assignable to type 'StateMachine<any, any, any, any> | { id?: string | undefined; src: string | StateMachine<any, any, any, any> | InvokeCreator<any, any>; ... 4 more ...; onError?: string | ... 2 more ... | undefined; } | InvokeConfig<...>[] | undefined'.
              Type '{ id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }' is not assignable to type 'undefined'.
  Overload 2 of 2, '(config: MachineConfig<MachineContext, any, AnyEventObject>, options?: Partial<MachineOptions<MachineContext, AnyEventObject>> | undefined, initialContext?: MachineContext | undefined): StateMachine<...>', gave the following error.
    Type '{ idle: { on: { FETCH: string; }; }; updateUser: { invoke: { id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<...>; onDone: { ...; }; onError: { ...; }; }; }; success: {}; failure: { ...; }; }' is not assignable to type 'StatesConfig<MachineContext, any, AnyEventObject>'.
      Property 'updateUser' is incompatible with index signature.
        Type '{ invoke: { id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }; }' is not assignable to type 'StateNodeConfig<MachineContext, any, AnyEventObject>'.
          Types of property 'invoke' are incompatible.
            Type '{ id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }' is not assignable to type 'StateMachine<any, any, any, any> | { id?: string | undefined; src: string | StateMachine<any, any, any, any> | InvokeCreator<any, any>; ... 4 more ...; onError?: string | ... 2 more ... | undefined; } | InvokeConfig<...>[] | undefined'.
              Type '{ id: string; src: (ctx: MachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: App; user: { data: any; name: string; }; }>; onDone: { ...; }; onError: { ...; }; }' is not assignable to type 'undefined'.ts(2769)
types.d.ts(267, 5): The expected type comes from property 'states' which is declared here on type 'MachineConfig<MachineContext, any, AnyEventObject>'
types.d.ts(267, 5): The expected type comes from property 'states' which is declared here on type 'MachineConfig<MachineContext, any, AnyEventObject>'

Reproduction
Here's the smallest reproduction I can come up with. My actual use case involves a bigger machine, with a more complex error message.

import { Machine, assign, EventObject } from 'xstate';

interface MachineContext {
  userId: number
  user: any
  error: any
}

interface User {
  name: string
  data: any
}

interface App {
  config: any
}

interface InitAndUpdateUserEvent {
  user: User
  app: App
}

const userMachine = Machine<MachineContext>({
  id: 'user',
  initial: 'idle',
  context: {
    userId: 42,
    user: undefined,
    error: undefined
  },
  states: {
    idle: {
      on: {
        FETCH: 'updateUser'
      }
    },
    updateUser: {
      invoke: {
        id: 'updateUser',
        src: async (ctx: MachineContext, {app, user}: EventObject&InitAndUpdateUserEvent) => {
          const result = await window.fetch('some-stuff');
          const data = await result.json();
          return {app, user: { ...user, data }};
        },
        onDone: {
          target: 'signedIn',
          actions: assign<MachineContext, EventObject&InitAndUpdateUserEvent>(
            (ctx: MachineContext, {user, app}: EventObject&InitAndUpdateUserEvent) => {
              return {...ctx, user, app};
            }
          )
        },
        onError: {
          target: 'failure',
          actions: assign({ error: (context, event) => event.toString() })
        },
      },
    },
    success: {},
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  }
});

Just paste this into VSCode. It should complain immediately.

In case I'm doing something completely wrong: Any kind of feedback highly appreciated!

bug documentation typescript

Most helpful comment

Thanks, that fixed the warning in VSCode. Unfortunately it didn't fix the issue in my real, complex use case. The error message I get from TypeScript is huge with 80% of the types it complains about invisible due to "..." everywhere... that's really frustrating. As far as I can understand it, it complains about "Types of property 'invoke' are incompatible.", "Types of property 'src' are incompatible." and finally "Type '(ctx: AppMachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: firebase.app.App; user: User; }>' is not assignable to type 'InvokeCreator'."

I'm wondering how I would go about fixing such problems? Without intricate knowledge of implementations details of Xstate and without bothering you. What process could I follow to debug such things? Thanks in advance.

All 12 comments

The error is a bit obtuse, but just looking at the code, your invocation src should _not_ be an async function. Try refactoring it and you'll see the error go away.

Thanks, that fixed the warning in VSCode. Unfortunately it didn't fix the issue in my real, complex use case. The error message I get from TypeScript is huge with 80% of the types it complains about invisible due to "..." everywhere... that's really frustrating. As far as I can understand it, it complains about "Types of property 'invoke' are incompatible.", "Types of property 'src' are incompatible." and finally "Type '(ctx: AppMachineContext, { app, user }: EventObject & InitAndUpdateUserEvent) => Promise<{ app: firebase.app.App; user: User; }>' is not assignable to type 'InvokeCreator'."

I'm wondering how I would go about fixing such problems? Without intricate knowledge of implementations details of Xstate and without bothering you. What process could I follow to debug such things? Thanks in advance.

I'm running into the same issue with the base example.

+1 it's really really hard to even get a simple app working with typescript and the error messages definitely don't make it easier

I will investigate this soon.

Here is an example of the example in the docs, with types: https://codesandbox.io/s/nostalgic-darwin-jjpfl?file=/src/index.ts

The trick is to use DoneInvokeEvent<Whatever> in the assign type params. We're thinking about ways to simplify/properly infer this in V5.

Hi, sorry to comment on a closed issue, but I'm getting the exact same issue (utterly incomprehensible type errors of the form Types of property 'invoke' are incompatible... ) on 4.12.0, downgrading to 4.11.0 immediately makes them disappear, so there seems to be _some_ kind of regression, or the way the types are interacting has changed subtly in an undocumented way between minor versions.

Please always try to share a repro case in a runnable form - either by providing a git repository to clone or a codesandbox. OSS maintainers usually can't afford the time to set up the repro, even if exact steps are given and there are none here.

4.11

https://codesandbox.io/s/xstate-4110-invoke-notypeerror-8tjqu

4.12

https://codesandbox.io/s/xstate-4120-invoke-typeerror-ovr1s

Same code, only difference is one minor version bump. Note afaik works on anything less than 4.11 as well, actual code this is extracted from has been built in some form for last year

@DanCouper The workaround is the same: use DoneInvokeEvent<any>:

        onDone: {
          target: "authorised",
          actions: assign<AuthenticatorContext, DoneInvokeEvent<any>>({
            username: (_ctx, e) => e.data,
            errorMsg: ""
          })
        }

Thank you @davidkpiano (and thank you for the code sample).

Can we please, please, please get some updates to the docs re TS though, rather than just the tiny section there is atm (eg typed examples)? I wouldn't bothered if the library wasn't written in the language. It has been, at times, incredibly painful using TS with XState, most often the given (JS) examples don't work out of the box and fail with exceptionally cryptic errors, and that starts up the next cycle of digging though the issues here to find someone with a similar issue

@DanCouper What specific updates would you like to see?

TypeScript is one of the most difficult parts about this library, since we're pushing the limits of what TS is able to provide re: logic safety within the context of statecharts, I know. There are some ways we're trying to alleviate the difficulty, such as with a TypeGen.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

carlbarrdahl picture carlbarrdahl  路  3Comments

amelon picture amelon  路  3Comments

doup picture doup  路  3Comments

kurtmilam picture kurtmilam  路  3Comments

suku-h picture suku-h  路  3Comments