Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sendParent has to returns an event of child type #711

Closed
codingedgar opened this issue Oct 9, 2019 · 4 comments · Fixed by #1178
Closed

sendParent has to returns an event of child type #711

codingedgar opened this issue Oct 9, 2019 · 4 comments · Fixed by #1178

Comments

@codingedgar
Copy link
Contributor

codingedgar commented Oct 9, 2019

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

@semopz
Copy link

semopz commented Nov 21, 2019

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
            })

@PetrBrabec
Copy link

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

@codingedgar
Copy link
Contributor Author

Thank you 💛

@tmikeschu
Copy link
Contributor

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants