Xstate: sendParent does not seem to work as the action of a spawned actor's invoked promise's onDone

Created on 7 Oct 2020  路  4Comments  路  Source: davidkpiano/xstate

Description

Sorry about the extremely wordy title.
I am working on a react/xstate project and trying to send events from a spawned machine to its parent. While observing in the xstate inspector, the event to be sent to the parent is never sent. I see talk of using asEffect to mitigate this in #1201 , but this doesn't make a lot of sense to me. The only examples I've seen of using asEffect is when redefining the actions of a machine when calling useMachine. How am I supposed to manipulate the actions of a machine that is being spawned by another machine?

While writing up this issue, I tried to replicate the bug in the codesandbox without react, and sendParent still doesn't work with a spawned actor. Not really sure what I'm doing wrong.

I made an extremely simplified version of the machines I'm using in my actual project.

After a little further digging, I noticed that I'm able to get my spawned actor to send an event to its parent if it's in the actions of a standard transition config, but _not_ in the onDone. Examples of both uses are included in the codesandbox below.

Expected Result

In the example, I'm expecting:

  1. listMachine should spawn an actor using the search machine and assign it to context.search.
  2. alertparent event is sent to spawned search actor
  3. search machine should send {type: 'alert'} to listMachine
  4. listMachine should receive alert and logs to the console.
  5. submit event is sent to spawned search actor, which transitions to the fetching state
  6. done.invoke.search.fetching:invocation[0] is sent with { data: [ 'a', 'b', 'c'] }
  7. search machine should send { type: 'results', data: ['a', 'b', 'c'] } to listMachine
  8. listMachine should receive results, assign it to the context.results, and log to the console.

Actual Result

  1. listMachine should spawn an actor using the search machine and assign it to context.search.
  2. alertparent event is sent to spawned search actor
  3. search machine should send {type: 'alert'} to listMachine
  4. listMachine should receive alert and logs to the console.
  5. submit event is sent to spawned search actor, which transitions to the fetching state
  6. done.invoke.search.fetching:invocation[0] is sent with { data: [ 'a', 'b', 'c'] }
  7. Nothing else happens.

Reproduction

https://codesandbox.io/s/spawn-broken-with-sendparent-hhu3c?file=/src/index.js

Additional context

I am trying to use the spawn approach instead of the invoke approach because I'd like to keep the search machine alive regardless of what the list machine is doing.

xstate version: 4.13.0

invalid

Most helpful comment

This is invalid:

      actions: (_, event) => {
        const toSend = sendParent("results", event.data);
        console.log("fetch completed, event to send:", toSend);
        return toSend;
      },

An action function is fire-and-forget; the return value is completely discarded. You should refactor this:

actions: sendParent((_, event) => ({
  type: 'results',
  data: event.data
})

Or use pure():

actions: pure((_, event) => {
  // ...

  return sendParent({ type: 'results', data: event.data })
})

All 4 comments

I am trying to use the spawn approach instead of the invoke approach because I'd like to keep the search machine alive regardless of what the list machine is doing.

You can invoke on the root node to do this:

const machine = createMachine({
  // ...
  invoke: {
    // ...
  }
});

I am trying to use the spawn approach instead of the invoke approach because I'd like to keep the search machine alive regardless of what the list machine is doing.

You can invoke on the root node to do this:

const machine = createMachine({
  // ...
  invoke: {
    // ...
  }
});

I made that change to the sandbox and I have the same issue: https://codesandbox.io/s/root-invoke-broken-with-sendparent-qlze1

This is invalid:

      actions: (_, event) => {
        const toSend = sendParent("results", event.data);
        console.log("fetch completed, event to send:", toSend);
        return toSend;
      },

An action function is fire-and-forget; the return value is completely discarded. You should refactor this:

actions: sendParent((_, event) => ({
  type: 'results',
  data: event.data
})

Or use pure():

actions: pure((_, event) => {
  // ...

  return sendParent({ type: 'results', data: event.data })
})

@davidkpiano Looks like I misinterpreted the different custom signatures sendParent is meant to expect. Thanks for your help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mattiamanzati picture mattiamanzati  路  3Comments

kurtmilam picture kurtmilam  路  3Comments

drmikecrowe picture drmikecrowe  路  3Comments

greggman picture greggman  路  3Comments

rodinhart picture rodinhart  路  3Comments