Xstate: sendParent has to returns an event of child type

Created on 9 Oct 2019  路  4Comments  路  Source: davidkpiano/xstate

Description

Using sendParent with types (TEvent) constraints type to the events of the child.

TodosMachineEvent != TodoMachineEvent
Expected Result

sendParent should be constraint to send parent events, event though the ExprWithMeta event should be of type of the child

deleted: {
    onEntry: sendParent<TodoMachineContext, TodoMachineEvent, TodosMachineEvent>((ctx: TodoMachineContext, e: TodoMachineEvent):TodosMachineEvent  => ({ type: "TODO.DELETE", id: ctx.id }))
}

Actual Result

This is not assignable to todoMachine in TodoMVC example, sendParent expects the TEvent to be the type of the child machine event

deleted: {
    onEntry: sendParent<TodoMachineContext,TodosMachineEvent>(ctx => ({ type: "TODO.DELETE", id: ctx.id }))
}

Reproduction

This is a simplified version with the templates
https://codesandbox.io/s/xstate-typescript-error-send-parent-jpzvn

import { Machine, interpret, spawn, assign, sendParent } from "xstate";
// import "./styles.css";

document.getElementById("app").innerHTML = `
<h1>XState TypeScript Example</h1>
<div>
  Open the <strong>Console</strong> to view the machine output.
</div>
`;

interface ChildContext {}
interface ChildEvent {
  type: "CHILD";
}

const child = Machine<ChildContext, any, ChildEvent>({
  id: "child",
  initial: "start",
  states: {
    start: {
      onEntry: [sendParent({ type: "PARENT" })]
//      ^ the problem (1)
    }
  }
});

// Edit your machine(s) here
interface MachineContext {
  count: number;
  childRef?: any;
}

type MachineEvent = { type: "PARENT" } | { type: "TOGGLE" };

const machine = Machine<MachineContext, any, MachineEvent>({
  id: "machine",
  initial: "start",
  context: {
    count: 0
  },
  states: {
    start: {
      on: {
        TOGGLE: "inactive"
      }
    },
    inactive: {
      onEntry: [],
      on: {
        TOGGLE: {
          target: "active",
          actions: [
            () => console.log("hi"),
            assign<MachineContext>({
              childRef: spawn(child)
//                               ^ some other problem (2)
            })
          ]
        }
      }
    },
    active: {
      on: {
        TOGGLE: "inactive"
      }
    }
  }
});

// Edit your service(s) here
const service = interpret(machine).onTransition(state => {
  console.log(state.value);
});

service.start();

service.send("TOGGLE");
service.send("TOGGLE");

(1) the error is:

Type 'SendAction<ChildContext, { type: "PARENT"; }>[]' is not assignable to type 'SingleOrArray<Action<ChildContext, ChildEvent>>'.
  Type 'SendAction<ChildContext, { type: "PARENT"; }>[]' is not assignable to type 'Action<ChildContext, ChildEvent>[]'.
    Type 'SendAction<ChildContext, { type: "PARENT"; }>' is not assignable to type 'Action<ChildContext, ChildEvent>'.
      Type 'SendAction<ChildContext, { type: "PARENT"; }>' is not assignable to type 'string'.ts(2322)
types.d.ts(267, 5): The expected type comes from property 'onEntry' which is declared here on type 'StateNodeConfig<ChildContext, any, ChildEvent>'

(2) Also, In this example I get this warning in the console

Warning: Attempted to spawn an Actor (ID: "child") outside of a service. This will have no effect.

Not sure what is that, I understand that service.start(); should be done, and it is, so i'm not sure of the problem here

Additional context

xstate@^4.7.0-rc3

bug typescript

Most helpful comment

This is great! I'm declaring a wrapper for the child machine so I only have to type the generics once:

import { sendParent as sendParentX } from 'xstate'

// ... other imports and declarations for parent/child types

const sendParent = (e: ParentEvent | ParentEvent["type"]): ReturnType<typeof sendParentX> =>
  sendParentX<ChildContext, ChildEvent, ParentEvent>(e)

All 4 comments

As a workaround, I just put any as the event type for sendParent (second type argument).

Have a look at line 75 / notifyClientV4 in machines.ts (only version without an error).
https://codesandbox.io/s/inspiring-newton-3ywkw

So in the example above, you could do this:

start: {
      onEntry: [sendParent<ChildContext, any>({ type: "PARENT" })]
//      ^ Shouldn't be a problem anymore
    }

EDIT: sidenote, this is how I normally "fix" your second issue

import { Interpreter } from 'xstate';
Interface ChildStateSchema {...}
// ...
type ChildActor = Interpreter<ChildContext, ChildStateSchema, ChildEvent>;
// ...
actions: [
            () => console.log("hi"),
            assign<MachineContext>({
              childRef: spawn(child) as ChildActor
//                               ^ some other problem should be ok now
            })

Is someone working on this? Or may I help with that?

Thank you 馃挍

This is great! I'm declaring a wrapper for the child machine so I only have to type the generics once:

import { sendParent as sendParentX } from 'xstate'

// ... other imports and declarations for parent/child types

const sendParent = (e: ParentEvent | ParentEvent["type"]): ReturnType<typeof sendParentX> =>
  sendParentX<ChildContext, ChildEvent, ParentEvent>(e)
Was this page helpful?
0 / 5 - 0 ratings