Feature Request
Since functions are first-class citizens in JavaScript, and in the interest of reducing boilerplate, I suggest that function actions would be more ergonomic than strings or objects (in many cases).
New Syntax:
const initTimer = seconds => () => {
// use seconds to initiate a timer
}
const machine = Machine({
initial: '1',
states: {
'1': {
on: {
timer: '2'
},
onEntry: initTimer(30)
},
'2': {
on: {
timer: '3'
},
onEntry: initTimer(45)
},
'3': {
on: {
timer: '1'
},
onEntry: initTimer(60)
}
}
})
Current Syntax:
const actionMap = {
initTimer: ({ seconds }) => {
// use seconds to initiate a timer
}
}
const machine = Machine({
initial: '1',
states: {
'1': {
on: {
timer: '2'
},
onEntry: { type: 'initTimer', seconds: 30 }
},
'2': {
on: {
timer: '3'
},
onEntry: { type: 'initTimer', seconds: 45 }
},
'3': {
on: {
timer: '1'
},
onEntry: { type: 'initTimer', seconds: 60 }
}
}
})
If this is a breaking change
I don't see this as a breaking change. Actions are already polymorphic (either string or object), and it's the user's responsibility to determine what type an action is and what to do with it / how to handle it. In effect, this is likely to be a documentation change plus a ts change to allow actions of type function. This would signal to users that actions can be functions and that this construction is specifically supported by project maintainers.
If this is part of the existing SCXML specification
It's a variation on the definition of actions in the SCXML spec.
I'm not a typescript whiz, but looking through the code, it seems the only change other than documentation might be to this line in types.ts.
From:
export type Action = string | ActionObject;
To:
export type Action = string | function | ActionObject;
In addition to reducing boilerplate in the actionMap and machine definitions, I'm pretty sure actions as functions would reduce the amount of code the user would need to write in their action handlers.
This was my initial thought when adding the actions syntax; however, w.r.t. statecharts it comes with some (probably non-obvious) disadvantages:
namedTimer(30) doesn't create a function called "namedTimerWith30Seconds" whereas { type: 'namedTimer', seconds: 30 } is fully descriptive.toString() them or something like that.// String-based actions
// You can assert that state.actions contains 'foo', for instance.
State {
value: ...,
actions: ['foo', 'bar', 'baz']
}
// Function-based actions
// More difficult to assert that the correct actions are called
State {
value: ...,
actions: [function, function, function]
}

I know @mogsie has similar thoughts on this, and yes, I realize this seems more boilerplatey, but I encourage you to think of this as a better long-term developer experience rather than a short-term developer experience. To the compiler, functions are, for the most part, opaque -- in that their output cannot be known until it is executed.
To see another good example of the motivation behind this syntax, take a look at how redux-saga handles declarative effects::
// Effect -> call the function Api.fetch with `./products` as argument
{
CALL: {
fn: Api.fetch,
args: ['./products']
}
}
Actions as functions will be supported in 3.3, and they're already in master 馃帀
Most helpful comment
This was my initial thought when adding the actions syntax; however, w.r.t. statecharts it comes with some (probably non-obvious) disadvantages:
namedTimer(30)doesn't create a function called"namedTimerWith30Seconds"whereas{ type: 'namedTimer', seconds: 30 }is fully descriptive.toString()them or something like that.I know @mogsie has similar thoughts on this, and yes, I realize this seems more boilerplatey, but I encourage you to think of this as a better long-term developer experience rather than a short-term developer experience. To the compiler, functions are, for the most part, opaque -- in that their output cannot be known until it is executed.
To see another good example of the motivation behind this syntax, take a look at how redux-saga handles declarative effects::