Description
When setting actions in the state machine option object, the event type does not have the right property. It seems it uses the first event object from the Event type.
Expected Result
I expected I can use any event object that I have defined in my Event object type.
Actual Result
Typescript throws an error:
Property 'file' does not exist on type 'UploadFileEvent'.
Property 'file' does not exist on type '{ type: "SUBMIT"; }'
Reproduction
import {
Machine,
sendParent,
State,
SingleOrArray,
OmniEvent,
assign
} from "xstate";
export interface UploadFileStateSchema {
states: {
idle: {};
incomplete: {};
};
}
// The events that the machine handles
export type UploadFileEvent =
| {
type: "ADD_FILE";
file: File;
}
| {
type: "SUBMIT";
};
export type UploadFileStateMachineContext = {
file: File;
};
export type UploadFileStateMachineState = State<
UploadFileStateMachineContext,
UploadFileEvent
>;
export type UploadFileStateMachineSend = (
event: SingleOrArray<OmniEvent<UploadFileEvent>>,
payload?: Record<string, any> & {
type?: undefined;
}
) => State<UploadFileStateMachineContext, UploadFileEvent>;
export const uploadFileStateMachine = Machine<
UploadFileStateMachineContext,
UploadFileStateSchema,
UploadFileEvent
>(
{
initial: "idle",
states: {
idle: {
on: {
ADD_FILE: {
actions: "addFile"
},
SUBMIT: [
{
cond: "checkFile",
actions: "submit"
},
{ target: "incomplete" }
]
}
},
incomplete: {
on: {
ADD_FILE: {
actions: "addFile"
},
SUBMIT: [
{
cond: "checkFile",
actions: "submit"
},
{ target: "incomplete" }
]
}
}
}
},
{
actions: {
submit: sendParent((context, event) => ({
type: "SUBMIT_UPLOAD_FILE",
file: context.file
})),
addFile: assign({
file: (context, event) => event.file
})
},
guards: {
checkFile: (context, event) => !!context.file
}
}
);
Additional context
xstate 4.7.2, same error 4.6
see https://github.com/davidkpiano/xstate/issues/836#issuecomment-560154725
I know it's not ideal in your case since that action is referred to more than once. You could do this (which also isn't ideal)
actions: {
addFile: assign<UploadFileStateMachineContext , { type: "ADD_FILE", file: File }>({
file: (context, event) => event.file
})
},
Not ideal obviously as you'd need to update your action if you later decide to make a change to your UploadFileEvent type (but at least TS will give you an error, reminding you to do that)
Okay, I hope this will work someday.
Here is my workaround for now:
export type UploadFileEvent = AddFileEvent | SubmitEvent;
type AddFileEvent = {
type: "ADD_FILE";
file: File;
};
type SubmitEvent = {
type: "SUBMIT";
};
addFile: assign({
file: (context, event: AddFileEvent) => event.file
})
At the moment TS inference fails us when it comes to inferring Context for assign and unfortunately once you specify a type argument you bail out from the inferring for all arguments imemdiately. So you cannot parametrize with Context, but leave Event to be inferred. There are issues about this on TS boards and they are figuring this out - but there is no ETA for this. So I'm afraid that at the moment there is no much we can do until that gets implemented in TS.
At the moment TS inference fails us when it comes to inferring Context for
assignand unfortunately once you specify a type argument you bail out from the inferring for all arguments imemdiately. So you cannot parametrize with Context, but leave Event to be inferred. There are issues about this on TS boards and they are figuring this out - but there is no ETA for this. So I'm afraid that at the moment there is no much we can do until that gets implemented in TS.
Ok, c'est la vie. Feel free to close this issue if you want to.
Any updates?
Most helpful comment
At the moment TS inference fails us when it comes to inferring Context for
assignand unfortunately once you specify a type argument you bail out from the inferring for all arguments imemdiately. So you cannot parametrize with Context, but leave Event to be inferred. There are issues about this on TS boards and they are figuring this out - but there is no ETA for this. So I'm afraid that at the moment there is no much we can do until that gets implemented in TS.