xstate icon indicating copy to clipboard operation
xstate copied to clipboard

Bug: `AssignAction` type error when using `exactOptionalPropertyTypes: true`

Open SandroMaglione opened this issue 1 year ago • 11 comments

XState version

XState version 5

Description

An assign action reports a type error when using exactOptionalPropertyTypes: true in tsconfig.json.

Expected result

It should be possible to use exactOptionalPropertyTypes: true with xstate.

Actual result

Reproduction

https://stackblitz.com/edit/vitejs-vite-lbw4wa?file=src%2Fmain.ts

Additional context

exactOptionalPropertyTypes allows for more type-safety. It is also recommended when using @effect/schema.

SandroMaglione avatar Dec 22 '23 04:12 SandroMaglione

@Andarist is there any workaround for this? Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

SandroMaglione avatar Apr 29 '24 01:04 SandroMaglione

@Andarist is there any workaround for this? Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

I updated XState and TypeScript in this codebase and it seems to work fine: https://stackblitz.com/edit/vitejs-vite-lbw4wa?file=package.json,src%2Fmain.ts

Can you double-check that everything's working on your end?

davidkpiano avatar Apr 29 '24 03:04 davidkpiano

@davidkpiano it seems to be working now when only actions are defined.

When instead I add also actors (as I need in my codebase) the issue comes back: https://stackblitz.com/edit/vitejs-vite-aesqqz?file=package.json,src%2Fmain.ts

SandroMaglione avatar Apr 29 '24 06:04 SandroMaglione

@Andarist is there any workaround for this?

I wasn't yet able to figure out a workaround. It's definitely possible to fix this on our side but it requires prioritization and time.

Do you know if it's possible to disable exactOptionalPropertyTypes for a specific file?

No, it's a global setting.

Andarist avatar Apr 29 '24 07:04 Andarist

Facing similar issue. Will really appreciate if any workaround is available. Thanks for your time and effort

bhvngt avatar Jul 12 '24 18:07 bhvngt

Facing similar issue. Will really appreciate if any workaround is available. Thanks for your time and effort

Can you share the code that's causing the issue for you?

davidkpiano avatar Jul 12 '24 18:07 davidkpiano

sure..

import { assign, createActor, fromPromise, setup } from "xstate";

const machine = setup({
  types: { context: {} as { value: number } },
  actors: {
    child: fromPromise(() => Promise.resolve(10))
  }
}).createMachine({
  id: "test",
  initial: "inactive",
  context: () => ({ value: 1 }),
  invoke: {
    src: "child",
    onDone: {
      actions: assign(({ event }) => ({ value: event.output }))
    }
  },
  states: {
    active: { on: { toggle: "inactive" } },
    inactive: { on: { toggle: "active" } }
  }
});

const actor = createActor(machine);
actor.subscribe(console.log);
actor.start();

Getting following error

error TS2322: Type '{ actions: ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>; }' is not assignable to type 'SingleOrArray<TransitionConfigOrTarget<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, ... 4 more ..., MetaObject>>'.
  Types of property 'actions' are incompatible.
    Type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'Actions<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject> | undefined'.
      Type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'ActionFunction<{ value: number; }, DoneActorEvent<number, string>, AnyEventObject, undefined, { src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
        Types of property '_out_TActor' are incompatible.
          Type 'ProvidedActor' is not assignable to type '{ src: "child"; logic: PromiseActorLogic<number, NonReducibleUnknown, EventObject>; id: string | undefined; }'.

43         onDone: {
           ~~~~~~

bhvngt avatar Jul 12 '24 19:07 bhvngt

I'm also seeing this sort of error when exactOptionalPropertyTypes is true. I'm using...

  • xstate 5.15.0
  • TypeScript 5.5.2

Example:

import { timer } from 'rxjs';
import { setup, fromObservable, createActor, assign } from 'xstate';

const machine = setup({
  types: { context: {} as { value: number }, events: {} as { type: 'start' } },
  actors: {
    child: fromObservable(() => timer(5_000)),
  },
}).createMachine({
  id: 'test',
  initial: 'notStarted',
  context: { value: 1 },
  states: {
    notStarted: {
      on: {
        // Type 'ProvidedActor' is not assignable to type '{ src: "child";...
        start: {
          target: 'running',
          actions: assign({
            value: ({ context }) => context.value + 1,
          }),
        },
      },
    },
    running: {
      invoke: {
        src: 'child',
        onDone: {
          target: 'done',
        },
      },
    },
    done: { type: 'final' },
  },
});

const actor = createActor(machine);
actor.subscribe((snapshot) => console.log(snapshot.value, snapshot));
actor.start();

document.addEventListener('click', () => actor.send({ type: 'start' }));

Produces the following error on the indicated line in the example (start:)

Type '{ target: string; actions: ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>; }' is not assignable to type 'TransitionConfigOrTarget<{ value: number; }, { type: "start"; }, { type: "start"; }, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, ... 4 more ..., MetaObject>'.
  Types of property 'actions' are incompatible.
    Type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'Actions<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject> | undefined'.
      Type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, ProvidedActor, never, never, never, never>' is not assignable to type 'ActionFunction<{ value: number; }, { type: "start"; }, { type: "start"; }, undefined, { src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }, never, never, never, EventObject>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
        Types of property '_out_TActor' are incompatible.
          Type 'ProvidedActor' is not assignable to type '{ src: "child"; logic: ObservableActorLogic<0, NonReducibleUnknown, EventObject>; id: string | undefined; }'.ts(2322)

michael-wisely-gravwell avatar Jul 18 '24 22:07 michael-wisely-gravwell

I'm also seeing this type error with exactOptionalPropertyTypes: true with

  • xstate 5.17.4
  • typescript 5.1.3

As @SandroMaglione noted, the issue only manifests when at least one actors is defined. But the weird part is that it is not localized to actions with actors... As soon as an actor is defined, even assign actions that are not connected to actors also get type errors.

Here's a variation of @bhvngt's example to illustrate:

import { assign, createActor, fromPromise, setup } from 'xstate';

const machine = setup({
  types: { context: {} as { value: number } },
  /////////// Comment this chunk out and the type error on "toggle" disappears
  actors: {
    child: fromPromise(() => Promise.resolve(10)),
  },
  ///////////////// end chunk
}).createMachine({
  id: 'test',
  initial: 'inactive',
  context: () => ({ value: 1 }),
  states: {
    active: {
      on: {
        // Type error on `toggle` here
        toggle: { target: 'inactive', actions: assign({ value: () => 0 }) },
      },
    },
    inactive: { on: { toggle: 'active' } },
  },
});

const actor = createActor(machine);

Link to stackblitz: https://stackblitz.com/edit/node-lsnym4?file=index.ts

khusmann avatar Aug 19 '24 16:08 khusmann