Replies: 11 comments
-
I don't think there is any particular reason for this discrepancy. I think that we should just unify both in the upcoming v5 and drop support for |
Beta Was this translation helpful? Give feedback.
-
For V5, here's what we should do:
|
Beta Was this translation helpful? Give feedback.
-
A little sad to lose |
Beta Was this translation helpful? Give feedback.
-
We already use a certain "protocol" for sent events (that it is an object with a type property) and we send such to actors. IMHO it's a good thing to enforce this simple (and familiar to most) protocol as it simplifies the implementation in most cases and makes things more predictable by removing the need for defensive code. Seems like a good common denominator that should/could be enforced. If one really, really needs to send thing of a different shape they can always implement deserialization logic on the receiving end of things.
Wouldnt this bring confuse people that it's allowed by the "integrations" and not by the core? What is the benefit of accepting those by the integrations? |
Beta Was this translation helpful? Give feedback.
-
It will not and it has not (or at least I haven't heard anything about it yet). It's a developer experience item, influenced by Vuex: https://vuex.vuejs.org/guide/actions.html#dispatching-actions If you feel strongly about removing it, we can ask the community what they think, but I feel like it's a harmless and useful addition. |
Beta Was this translation helpful? Give feedback.
-
@akbr A simple change can be made to allow passing strings to |
Beta Was this translation helpful? Give feedback.
-
Just a note - I was strictly advocating for removing
IMHO it's (
which is rather far from "easy". |
Beta Was this translation helpful? Give feedback.
-
I suppose a good compromise would be a helper function: import { eventFrom } from 'xstate/event';
// ...
send(eventFrom('CHANGE', { field: 'name', value: 'alice' })); EDIT: modified my comment above |
Beta Was this translation helpful? Give feedback.
-
I want to drop into this thread an example of something that makes My goal is to be able to have semi-dynamic dispatch. You know where you are, and where you need to go, but the receiving state needs additional information (known statically in advance) in order to continue. I don't see it to be remarkably different from I've read into the 4.X code to figure out how to insert this behavior. The most-relevant piece is this: sendWithPayload(
'ALERT',
{
data: {
prompt: 'This window has timed out. What would you like to do?',
answers: [
{ label: 'Keep Waiting', action: 'WAIT' },
{ label: 'Try Again', action: 'RETRY' },
{ label: 'Give Up', action: 'ABORT' }
]
}
}
) This traverses up through the state machine, and then back down into a singular, modal, alert component. (Think A full version is below. Machine Definitionimport { actions, assign, spawn, Machine, interpret, send, sendParent } from 'xstate';
// Piggyback on the send action creator, mimic the actual behavior behind the scenes.
function sendWithPayload(action, payload) {
var temp = send(action);
payload.type = action;
Object.assign(temp.event, payload);
return temp;
}
// This is a window manager. Multiple windows (child components).
function applicationConfigGen() {
return {
id: 'application',
type: 'parallel',
states: {
// This thing can timeout, necessitating an alert.
'child-component1': childComponentConfigGen(),
// This is a singleton across the entire window manager.
'alert': alertComponentConfigGen()
},
on: {
// This is far-more-clear to me than '*' with dispatch based upon `JSON.parse(the event name)`.
// This thing doesn't actually care about the payload, it just needs to direct it
// to the correct place.
'ALERT': {
actions: [
actions.pure((context, event) => { return sendWithPayload('FOREGROUND', event)})
]
}
}
};
}
// This is a singleton. You can only have one alert at a time.
// This component is not, however, just an "OK" type of alert.
// It is designed to receive a configuration that would inform
// what content to display.
function alertComponentConfigGen() {
return {
id: 'alert-component',
initial: 'background',
states: {
// NOTE: this doesn't currently handle back-pressure or modality.
'background': {
on: {
'FOREGROUND': 'foregrounding'
}
},
'foregrounding': {
invoke: {
id: 'foregrounding-promise',
src: Machine(promiseConfigGen()),
onDone: 'foreground'
}
},
'foreground': {
on: {
'BACKGROUND': 'backgrounding'
}
},
'backgrounding': {
invoke: {
id: 'backgrounding-promise',
src: Machine(promiseConfigGen()),
onDone: 'background'
}
}
}
};
}
function childComponentConfigGen() {
return {
id: 'child-component',
initial: 'load',
states: {
'load': {
invoke: {
src: Machine(cancellablePromiseConfigGen()),
onDone: [
{
target: 'resolve',
cond: (context, event) => {
return event.data && event.data.target === 'resolve';
}
},
{
target: 'reject',
cond: (context, event) => {
return event.data && event.data.target === 'reject';
}
}
]
},
on: {
'TIMEOUT': 'timeout'
}
},
'timeout': {
entry: [
// Magic is here. We need an alert!
// The point of invocation knows where it is, and what can be done in this state.
sendWithPayload(
'ALERT',
{
data: {
prompt: 'This window has timed out. What would you like to do?',
answers: [
{ label: 'Keep Waiting', action: 'WAIT' },
{ label: 'Try Again', action: 'RETRY' },
{ label: 'Give Up', action: 'ABORT' }
]
}
}
)
]
},
'resolve': { type: 'final' },
'reject': { type: 'final' }
}
};
}
function cancellablePromiseConfigGen() {
return {
id: 'cancellable-promise',
initial: 'spawn',
context: {},
states: {
'reset': {
always: [
{
target: 'spawn',
actions: [
actions.pure((context) => { return actions.stop(context.promise.id); })
]
}
]
},
'spawn': {
always: [
{
target: 'waiting',
actions: [
assign({
promise: () => {
return spawn(Machine(promiseConfigGen()), { sync: true });
}
})
]
}
]
},
'waiting': {
after: {
1000: 'timeout'
},
on: {
'TIMEOUT': 'timeout'
}
},
'timeout': {
entry: [
sendParent('TIMEOUT')
],
on: {
ABORT: 'abort',
RETRY: { target: 'reset' },
WAIT: 'waiting'
}
},
'abort': { type: 'final', data: { target: 'abort' } },
'resolve': { type: 'final', data: { target: 'resolve' } },
'reject': { type: 'final', data: { target: 'reject' } }
},
on: {
'xstate.update': [
{
target: 'resolve',
cond: (context, event) => {
return event.state.done && event.state.value === 'resolve';
}
},
{
target: 'reject',
cond: (context, event) => {
return event.state.done && event.state.value === 'reject';
}
}
]
}
};
}
function promiseConfigGen() {
return {
id: 'invocable-promise',
initial: 'promise',
states: {
'promise': {
invoke: {
src: () =>
new Promise(function (resolve, reject) {
setTimeout(resolve, 2000);
}),
onDone: 'resolve',
onError: 'reject'
}
},
'resolve': { type: 'final' },
'reject': { type: 'final' }
}
};
}
const applicationMachine = Machine(applicationConfigGen());
const applicationInterpreter = interpret(applicationMachine);
applicationInterpreter.onTransition((state, event) => {
console.log('state:', state.value);
console.log('event:', event.type);
});
applicationInterpreter.start(); |
Beta Was this translation helpful? Give feedback.
-
How is it different from: send({
type: "ALERT",
data: {
prompt: "This window has timed out. What would you like to do?",
answers: [
{ label: "Keep Waiting", action: "WAIT" },
{ label: "Try Again", action: "RETRY" },
{ label: "Give Up", action: "ABORT" },
],
},
}); and actions.pure((context, event) => {
return send({ ...event, type: "FOREGROUND" });
}); I've tested locally both versions and they yield the same results as far as I can tell. |
Beta Was this translation helpful? Give feedback.
-
You're, of course, correct. 😊 I just didn't know how to (I appreciate the consistent "yo, you're making this harder on yourself than necessary" comments. I'm going to turn this into something, promise.) |
Beta Was this translation helpful? Give feedback.
-
service.send
will accept a string followed by an object payload. (https://xstate.js.org/docs/guides/interpretation.html#sending-events)But when invoking callbacks, the
callback
argument (which sends an event to the parent) does not. (https://xstate.js.org/docs/guides/communication.html#invoking-callbacks)Any reason for this discrepancy? I've tripped over this a few times now.
Beta Was this translation helpful? Give feedback.
All reactions