Parameters when transitioning states
Closed this issue · 3 comments
It looks like there is currently no way to provide external information when transitioning states, I read the source code for send
, and the examples (such as fetch) seem to internalise the external effect so that the data is available within effect
. Apologies if I did a poor job of looking.
My use case is moving data from a subscription callback into the state machine. I need to manage an external lifecycle (add/remove subscriber), and data is supplied to me via callback to the subscriber. Firebase's realtime database is a concrete example:
const [state, send] = useStateMachine({firebaseValue: …})({
initial: 'loading',
states: {
loading: {
on: {
SUCCESS: 'loaded',
FAILURE: 'error',
},
effect(_, update, /* ??? */) {
update(context => /* External value would be placed into the context here. */)
},
},
// …
},
})
React.useEffect(() => {
// A hypothetical `send` API with parameters.
const success = value => send('SUCCESS', value)
const failure = error => send('FAILURE', error)
const ref = firebase.database().ref('some/path')
// Manage the subscriber lifecycle.
ref.on('value', success, failure)
return () => {
ref.off('value', success)
}
)
I realise it would be possible to achieve this with a ref, but I would prefer not having one foot in the door in terms of state and context.
Hi, good question. This is by design - Ideally, changing the state machine context is always a consequence of the state machine itself transitioning. It should never come from the outside.
My suggestion is, as you noted, internalise the effect:
const [state, send] = useStateMachine({firebaseValue: …})({
initial: 'loading',
states: {
loading: {
on: {
SUCCESS: 'loaded',
FAILURE: 'error',
},
effect(send, update) {
const success = firebaseValue => {
update(context => ({firebaseValue, ...context}))
send('SUCCESS')
}
const failure = error => {
update(context => ({error, ...context}))
send('FAILURE')
}
const ref = firebase.database().ref('some/path')
// Manage the subscriber lifecycle.
ref.on('value', success, failure)
return () => {
ref.off('value', success)
}
},
},
// …
},
})
But this will unsubscribe as soon as the first piece of data is fetched. Another strategy would be using nested states, which this library doesn't support yet (working on it). A nested state would allow you to do something like this:
const [state, send] = useStateMachine({firebaseValue: …})({
initial: 'subscribe',
states: {
subscribe: {
initial: 'loading',
on: {
SUCCESS: 'active',
FAILURE: 'error',
},
states: {
loading: {},
active: {},
error: {}
},
effect(send, update) {
const success = value => {
update(context => ({value, ...context}))
send('SUCCESS')
}
const failure = error => {
update(context => ({error, ...context}))
send('FAILURE')
}
const ref = firebase.database().ref('some/path')
// Manage the subscriber lifecycle.
ref.on('value', success, failure)
return () => {
ref.off('value', success)
}
},
},
// …
},
})
This would start as subscribe > loading, then would either transition to subscribe > error or subscribe > active. The effect on the parent "subscribe" state would still be active, sending updates.
Right now, I would suggest using XState for this, and keep an eye on the progress on #21
Thanks for the clear and detailed reply, @cassiozen!
Closing for now, please feel free to reopen.