Question/Feature Request
I am in the process of building a multi step form. When a field is filled, I am firing an event. In state config, I assign the field value to context when event is fired. I need to now validate the context and if there are no errors, need to transition to next state. If there are errors, the error message should be put in context. How do I do this? Additionally, the validation might be asynchronous.
{
id: 'onboarding',
context: {
mobileNumber: null,
mobileNumberError: null
},
initial: 'mobileNumber',
states: {
mobileNumber: {
id: 'mobileNumber',
initial: 'invalid',
states: {
invalid: {
on: {
SET_MOBILE_NUMBER: {
actions: (context, event) => assign({mobileNumber: event.value}),
// Validate and assign error message. transition to valid if there is no error
},
},
},
valid: {
on: {
SET_MOBILE_NUMBER: {
actions: (context, event) => assign({mobileNumber: event.value}),
// Validate and assign error message, transition to invalid if there is error
},
FETCH_MOBILE_NUMBER_OTP: '#mobileNumberOtp',
},
},
},
},
mobileNumberOtp: {
id: 'mobileNumberOtp',
initial: 'creating',
states: {
creating: {
invoke: {
id: 'fetchMobileNumberOtp',
src: fetchMobileNumberOtp,
onDone: 'invalid',
onError: '#mobileNumber.valid',
},
},
},
}
NA
NA
NA
I am sure there is a way to do this already. You may want to put up a recipe of this in documentation.
NA
Here is what I tried later:
{
states: {
mobileNumber: {
id: 'mobileNumber',
initial: 'invalid',
on: {
SET_MOBILE_NUMBER: {
target: '.validating',
actions: (context, event) => assign({mobileNumber: event.value}),
},
},
states: {
validating: {
invoke: {
src: validateMobileNumber,
onDone: {
target: 'valid',
actions: assign({mobileNumberError: ''}),
},
onError: {
target: 'invalid',
actions: assign({
mobileNumberError: (context, event) => event.data,
}),
},
},
},
invalid: {},
valid: {},
},
},
}
Invoking service (with Promise) sounds like a good idea. I'm still learning about fsm but in the end I came up with similar solution https://codesandbox.io/s/serverless-http-1putt
Maybe we can get some pro hints from @davidkpiano ;)
...
validating: {
invoke: {
src: 'validateMobileNumber',
onDone: [
{
target: 'valid',
actions: assign({mobileNumberError: ''}),
cond: (_ctx, event) => {
const validationResponse = event.data;
if (validationResponse.result) { // I don't know what's in your response... Just an example
return true;
} else {
return false;
}
}
},
// If the condition is false, validation failed, so go to invalid
{
target: 'invalid',
actions: assign((_ctx, event) => ({ mobileNumberError: event.data.validationMessage }))
}
],
onError: { ... }
Yep, guarded transitions (linked above by @semopz) are what you want.
Interesting, I am also facing a similar issue but it involves multiple checks.
For instance. how would you validate a text input with multiple guards? min length, max length, invalid characters, maybe a regex as well. I am rewriting a multiform wizard that currently uses Formik and Yup, but ideally would like all logic the machine so it can be tested independently of Formik.
@jaetask You can probably come up with a solution in userland for this, no?
Most helpful comment
Guarded Transitions