xstate icon indicating copy to clipboard operation
xstate copied to clipboard

enqueue.assign() is not typesafe

Open ivankoleda opened this issue 11 months ago • 4 comments

XState version

XState version 5

Description

If we define types in setup function like this

setup({
    types: {} as {
      context: {
        foo: {
          bar: string;
        };
      };
    },
  })

And add a following action handler

 actions: [
  enqueueActions(({ enqueue }) => {
    enqueue.assign({
      // No TS error
      foo: undefined,
    });
  }),
],

There won't be a type error and accessing context.foo.bar will result in runtime error as context.foo can be undefined during runtime.

Doing the same with assign results in type error though:

actions: [
  assign({
    // TS error
    foo: () => undefined,
  })
]

Expected result

Typescript error

Actual result

No Typescript error

Reproduction

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

Additional context

No response

ivankoleda avatar Mar 20 '24 12:03 ivankoleda

You are not comparing the same flavor of assign here. If we want to compare the same flavors then we need to compare those two:

assign({
  foo: undefined,
}),
enqueueActions(({ enqueue }) => {
  enqueue.assign({
    foo: undefined,
  });
}),

And they both don't error here. The problem is that assign accepts a "partial assigned" - u are free to pass just some keys (and not everything the context has). The problem here is that TS (by default) always allows undefined value for optional properties (and Partial creates optional properties). One needs exactOptionalPropertyTypes to change this behavior and not many projects have that setting enabled.

Andarist avatar Mar 20 '24 16:03 Andarist

We can't address this as long as we allow property assigners derived from Partial<TContext>. So the Bug label here isn't quite fitting (cc @davidkpiano). It feels like a design limitation with the current API.

Andarist avatar Mar 23 '24 14:03 Andarist

I added the recommendation to use exactOptionalPropertyTypes in the TypeScript documentation https://stately.ai/docs/typescript#set-up-your-tsconfigjson-file

{
  "compilerOptions": {
    // ...
    "strictNullChecks": true,
    // or set `strict` to true, which includes `strictNullChecks`
    // "strict": true,
    "skipLibCheck": true,
    "exactOptionalPropertyTypes": true
  }
}

davidkpiano avatar Mar 24 '24 19:03 davidkpiano

@davidkpiano this likely should be removed from the docs for the time being since our types are actually not fully compatible with this flag, see https://github.com/statelyai/xstate/issues/4613

Andarist avatar Mar 24 '24 19:03 Andarist