Dependency Yup Custom Validation trong ReactJS (Phần 2)

Remind

Trong bài viết ở phần 1, mình đã nói về việc, làm sao có thể tạo cho app của mình 1 custom function yup validation, cụ thể:

import * as Yup from 'yup';

function requiredWith(ref, expectedValue, msg) {
  return this.test({
    name: 'requiredWith',
    exclusive: false,
    message: msg,
    test(value) { return (this.resolve(ref) !== expectedValue) || !!value); }, //value là giá trị hiện tại của field gọi method
  });
}

Yup.addMethod(Yup.mixed, 'requiredWith', requiredWith);

const exampleValidation = Yup.object().shape({
  selectField: Yup.string()
    .required('Required Field'), //default yup validation
  dependentSelectField: Yup.string()
    .requiredWith(Yup.ref('selectField'), 'Yes', 'Require when SelectField has Yes value'),
  inputField: Yup.string()
    .requiredWith(Yup.ref('selectField'), 'Yes, 'Require when SelectField has Yes value'),
});

Khi selectField có giá trị là "Yes" thì validate sẽ được bắt ở dependentSelectField và inputField, chúng ta lấy được giá trị của selectField nhờ việc refer ('ref') selectField vào và lấy giá trị thông qua resolve(ref).

Tuy nhiên, trong một số trường hợp, chúng ta bắt gặp việc dependent validate phải refer từ 1 field khác, nằm ngoài formik hiện tại, hoặc đơn giản là không thể refer vào yup validation hiện tại thì phải làm như thế nào.

Tham chiếu ngoài

Giả sử, cũng vẫn là tình trường hợp ở Phần 1, tuy nhiên lúc này selectField lại nằm ngoài formik của 2 fields ở dưới là dependentSelectField và inputField, bạn không thể refer Yup.ref('selectField') như trước nữa.

Lúc này, điều bạn cần là truyền giá trị của selectField vào trong exmpleValidation, có khá nhiều cách lấy giá trị hiện tại của selectField, trong bài viết này mình chọn cách setState, withState('selectValue', 'setSelectValue', 'No') ở đây mình dùng withState của thư viện 'recompose' để cho gọn và set default cho value là 'No'.

Ở component, mình sẽ bắt sự kiện để set value, cụ thể là:

<BooleanSelect
      value={selectValue}
      name="selectField"
      onChange={selectValue => setSelectValue(selectValue)}
/>

Lúc này, ở container, hơi khác với phần 1, chúng ta sẽ truyền thêm state selectValue vào trong exampleValidation:

withFormik({
    validationSchema: props => ExampleValidation(props.selectValue),
    ...

Khi đó, ở exampleValidation.js

import * as Yup from 'yup';

const exampleValidation = selectValue => (selectValue === 'Yes' ? Yup.object().shape({ // khi selectValue có giá trị là 'Yes' thì sẽ bắt require của dependentSelectField và inputField
  dependentSelectField: Yup.string()
    .required('Require when SelectField has Yes value'),
  inputField: Yup.string()
    .required('Require when SelectField has Yes value'),
}) : Yup.object());

Tạm kết

Về cơ bản, refer giá trị từ 1 field ngoài formik để phục vụ cho validation không khác gì nhiều với cách dependency validation trong cùng 1 formik, chỉ là cách ta xử lý và lấy được giá trị của field đó rồi truyền vào cho validation schema rồi thực hiện validate tuỳ mục đích cụ thể.

Ở một số trường hợp, không phải lúc nào cũng tối ưu khi viết ra hẵn 1 function riêng để thực hiện validate cho một field cụ thể. Đôi lúc, chúng tao tạo ra 1 custom validation chẳng hạn cho mục đích requirement 1 field bằng cách tham chiếu giá trị của 1 field nào đó nhưng còn những trường hợp khác khi cần phải tham chiếu 2-3 fields... Lúc này tạo ra một loạt các function để thực hiện cho từng mục đích riêng biệt thậm chí chỉ cho duy nhất 1 field thì thật là lãng phí và khó maintain sau này. Đó là lúc chúng ta sử dụng những hàm ngắn gọn hơn, tích hợp thẳng vào field ta cần validate. Ở phần thứ 3, mình sẽ giới thiệu cụ thể hơn về cách làm này và ưu, nhược điểm của nó đổi với việc tạo ra 1 function riêng biệt.