Xstate: Question: how to do send('something', {to: ctx.sendToAsStr }) in MachineOptions

Created on 2 Apr 2020  路  17Comments  路  Source: davidkpiano/xstate

This is a question

very much like this one
"Can I pass values when sending events from one machine to another in xState"

Description:

How to create an action in MachineOptions actions section that sends a message to a actor who's ID stored as a string in the context?

(Bug) Expected result:

I would like to say something like


// this does not work...
const myMachineOptions:Partial<MachineOptions<any,any>> = 
{
  actions:{
     mySend: (context, event)=>send('MY_EVENT_NAME', {to: context.sendToAsStr })
  }
}

question

Most helpful comment

Make to an expression, not the whole action.

send(EV, { to: ctx => ctx.actorId })

All 17 comments

Make to an expression, not the whole action.

send(EV, { to: ctx => ctx.actorId })

Hey @Andarist. I found @BorisDaich's question on StackOverflow and it led me here. I'm struggling with this setup in a Vue app. I have an alert-machine that's instantiated when I start my app. It's a simple machine that I send user notifications too. When I create machines in other files _(components)_ I'm not sure what's the best way of sending events to the alert machine as it's already a running service.

I tried send('ALERT', { to: 'alert', alert: 'TEST' }) and I get Unable to send event to child 'alert' from service. How does a machine send events to another running service? Do I import the service into other machines? With something like...

// main.js
import alertMachine from "./alert-machine";

export const alertService = interpret(alertMachine).start();
// campaign.js
import { useService } from "@xstate/vue";
import { alertService } from "@/main";
import campaignMachine from "./campaign-machine";

const { state, send } = useMachine(
  // Pass running service into campaign machine
  campaignMachine, { services: {alertService} }
);

@gilesbutler That has to be done within the machine itself. I know it's confusing, but the send(...) function that you get from service.send is different than the send(...) action creator.

So you can service.send('ALERT'), which will send it to the machine, which will know what to do with it; e.g.:

on: {
  ALERT: {
    actions: actions.send('ALERT', { to: 'alert' })
  }
}

Hopefully that makes sense.

I think the problem here might be that @gilesbutler wants to send an event to an arbitrary machine, whereas you can only send it to your children when sending it to an "id". Each machine has sessionId though which can be used for this but, ideally, you need to share this sessionId through events, put it into your sender's context and use it as a target then.

Keep in mind that we are aiming for explicitness with XState and we avoid magic, so there is no way to send an event to an "unknown" address - you need to obtain it first.

Right, an alternative would be to grab the "service" ref from state.children and send the event directly to the ref.

Hey @davidkpiano thanks for the response. Ha I must be missing something here still. Unfortunately I still get the following error Unable to send event to child 'alert' from service 'campaignMachine'. I don't see how my "campaignMachine" knows about the alert service which has been instantiated in a completely separate file. In your example...

on: {
  ALERT: {
    actions: actions.send('ALERT', { to: 'alert' })
  }
}

Are you assuming that the alert service (to: 'alert') is a service created via an invoke call in the same Machine?

Maybe I'm architecting this wrong but my assumption was I can have a running service in one file and I can send events into it from another Machine in a separate file. The service would keep on running and listening for events till it reaches it's final state. The missing piece for me is how does a Machine know about a running service from another file? Do I need to import that service into all the other machines with something like import { alertService } from "@/main";?

Really appreciate the help :)

@gilesbutler Please create a reproducible CodeSandbox example. No it doesn't know of arbitrarily created services; everything must be explicit.

Thanks @davidkpiano - I'm working on a CodeSandbox example, for some reason the exact same code that works in my local environment isn't working in there. Once I get it fixed I'll post here.

Hey @davidkpiano I can't get the UI working properly because I think it's something to do with the vue composition api plugin not working in the CodeSandbox environment.

Anyway I think the structure and what I'm trying to do with xstate is there. The main thing I'm trying to figure out is if this architecture makes sense. I'm importing the the alertService in the user-machine using import { alertService } from "../main"; here. Then once you click the refresh button in the rendered markup you can see in the console that the user-machine is sending events to the alert-service via the sendToAlertService action after the loading is complete here.

I'm trying to understand if that's the right way of doing things though? Should you be sending events to other services like this or should I transition to another state that invokes the imported service? Or is invoke only used to invoke new services?

_Side note:_ With regards to the UI not working, it should update with the message you send to the alert service by setting const msg = computed(() => state.value.context.alert); in here. This works on my local and development setups, just not in CodeSandbox. I assume to make the context reactive it needs to be wrapped in a computed prop or should the context already be reactive?

Thanks for your help.

Or is invoke only used to invoke new services?

This.


Ideally, you should have a common ancestor machine that would invoke alertService and pass its address to the other machine so they could communicate using send. Putting purity aside you can technically put imported alertService into the other machine's initial context and send to it using send expressions like:

send('something', to: ctx => ctx.alertService)

Thanks for the reply @Andarist 馃憤- just to clarify by...

This.

Do you mean yes that is the case that invoke is only used to invoke new services?

That makes sense re the common ancestor machine, it sounds like I'm architecting things slightly wrong then. In an ideal scenario do you have a parent machine that invokes all other machines in a tree like data structure?

Just so I have a deeper understanding, please can you clarify what you mean by...

Putting purity aside

Sorry for the basic questions, I'm pretty new to xState but really enjoying how powerful and helpful it is. Thanks for your help.

Do you mean yes that is the case that invoke is only used to invoke new services?

Yes, you can't invoke already running services.

In an ideal scenario do you have a parent machine that invokes all other machines in a tree like data structure?

Yes.

Putting purity aside

I have just meant that the ideal situation (where you have a single root machine which invokes everything else in a tree-like structure) might not be practical sometimes. You should strive to reach it, but if the cost of introducing it is too great for the moment then you can put an existing machine in the initial context to get your code working quicker.

Thanks for clarifying @Andarist that's really helpful, appreciate your support and I hope you're staying safe during the pandemic.

Hey @Andarist, I was just wondering if you know of any code examples of xState apps with the tree like architecture we were discussing? It would be super helpful to get my head around it a bit. I'm not quite sure how that architecture would work with say a vue or react setup and routing etc. Thanks.

I'm afraid that I don't know any OSS app using such architecture, but this has just been open-sourced (or just announced for a broader audience):
https://github.com/cypress-io/cypress-realworld-app

Might give you some inspiration.

Awesome thanks @Andarist I'll check it out 馃憤

Was this page helpful?
0 / 5 - 0 ratings