Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to assign and transition conditionally? #850

Closed
jaysson opened this issue Dec 6, 2019 · 6 comments
Closed

How to assign and transition conditionally? #850

jaysson opened this issue Dec 6, 2019 · 6 comments
Labels

Comments

@jaysson
Copy link

jaysson commented Dec 6, 2019

Bug or feature request?

Question/Feature Request

Description:

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',
          },
        },
    },
}

(Bug) Expected result:

NA

(Bug) Actual result:

NA

(Bug) Potential fix:

NA

(Feature) Potential implementation:

I am sure there is a way to do this already. You may want to put up a recipe of this in documentation.

Link to reproduction or proof-of-concept:

NA

@jaysson
Copy link
Author

jaysson commented Dec 7, 2019

Here is what I tried later:

Invoking a promise to validate

{
  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: {},
      },
    },
}

@RafalFilipek
Copy link

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 ;)

@semopz
Copy link

semopz commented Dec 9, 2019

Guarded Transitions

...
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: { ... }

@davidkpiano
Copy link
Member

Yep, guarded transitions (linked above by @semopz) are what you want.

@jaetask
Copy link

jaetask commented Mar 27, 2020

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.

@davidkpiano
Copy link
Member

@jaetask You can probably come up with a solution in userland for this, no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants