Yup: Adding validations for dynamic data in react

Created on 26 Jan 2017  路  7Comments  路  Source: jquense/yup

Using react formal with my schema statically defined (and reused):

export const AddressSchema = yup.object({
  category: string.nullable(),
  full_name: string.nullable(),
  address1: string.required(),
  address2: string.nullable(),
  city: string.required().min(2).max(30),
  state_code: string.required().min(2).max(2),
  postal_code: string.required().min(5).max(13),
  country_code: string.default('US').required().min(2).max(3),
  default_billing: boolean,
  default_shipping: boolean
})
export const ContactSchema = yup.object({
  first_name: string.required(),
  last_name: string.required(),
  email: string.required().email(),
  mobile_phone: string.required().phone(),
  work_phone: string.phone().nullable(),
  home_phone: string.phone().nullable(),
  addresses: edges(AddressSchema).min(1)
})

I have a dynamic dataset provided that gives me viable subregions (or states). How can I tack on a dynamic test to state_code inside a render or componentWillMount?

Most helpful comment

Working state validation - dependent on context and other field:

  state_code: string.required().min(2).max(2)
    .test('validState', '${path} is not a valid state',
      function (value) {
        if (!value || (value !== null && value.length === 0)) {
          return false
        }

        console.log('test options', this.options)
        const { context, parent: address } = this.options
        const addressRegions = context.addressRegions() 
        const regionCode = address.country_code
        const result = addressRegions.subregions[ regionCode ].includes(value)
        return result
      }),

All 7 comments

Pseudocode is something like:

render() {
  const schema = ContactSchema.clone()
  schema.fields.addresses.???.
  .test('validState', '${path} is not a valid state', 
     value => this.props.subregions.includes(value))
}

you can do that ^ however validation is all async so doing a test in render of lifecycle method won't work.

also instead of cloning you can pass arbitrary data to a cast/validation via the context option and access it in the test on this.options.context

Understood that it is async, and thanks for pointing me to context - that looks workable.

My issue is a bit quirky in that I have multiple address subregions (states) to verify which could be in multiple regions (countries).

So state_code verification depends on also knowing the country_code value.

addressRegions is an object that contains information about all addresses (unique set of regions and subregions as lookup values).

So, here's some pseudocode:

  country_code: string.default('US').required().min(2).max(3),
  state_code: string.required().min(2).max(2)
    .test('validState', '${path} is not a valid state',
      value => {
        console.log('test options', this.options)

        const { context, parent: address } = this.options
        const addressRegions = context.addressRegions() // how to access context?
        const regionCode = this.options.country_code // how to access other field value?

        return addressRegions.subregions[ regionCode ].includes(value)
      }),

<Form context={{addressRegions}} />

I'm poking around further but any quick advice is appreciated. Thank you.

Follow up to this - got it.

this.options.context gives context.
this.options.parent gives parent object values.

Awesome.

@jquense - i have something strange with this. In my debugger inside the given function, I can see this. But if I console.log(this.options) - I get undefined. Am I authoring this function incorrectly to get the desired this context? (code above updated)

Ugh - can't use arrow functions here if I want this. No problem, just confusion.

Working state validation - dependent on context and other field:

  state_code: string.required().min(2).max(2)
    .test('validState', '${path} is not a valid state',
      function (value) {
        if (!value || (value !== null && value.length === 0)) {
          return false
        }

        console.log('test options', this.options)
        const { context, parent: address } = this.options
        const addressRegions = context.addressRegions() 
        const regionCode = address.country_code
        const result = addressRegions.subregions[ regionCode ].includes(value)
        return result
      }),
Was this page helpful?
0 / 5 - 0 ratings