Validate with redux-form (part 2)

Trong bài viết trước Part 1 tôi đã nói về cách làm thế nào để validate một form đơn giản với redux-form. Trong bài viết tiếp theo này tôi sẽ giới thiệu một số kĩ thuật phức tạp hơn như validate nested form, khởi tạo dữ liệu cho form

1. Validate nested form với redux-form

Với những form phức tạp việc sử dụng nested form với các mối quan hệ là không thể tránh khỏi. Các bạn thường nghĩ là cấu trúc nested form khá là rắc rối và phức tạp bởi vì nó liên quan tới các quan hệ trong form. Vậy việc validate cho nested form với redux-form có khó không? chúng ta cùng tìm hiểu nhé.

Như trong bài viết trước các bạn đã biết redux-form quản lí các field (thuộc tính cần validate) thông qua component Field, mỗi thuộc tính được định danh thông qua trường name của component Field. Ví dụ ta có một Component như sau

class Address extends Component {
    render() {
        return <div>
            <Field name="streetName" component="input" type="text"/>
            <Field name="number" component="input" type="text"/>
            <Field name="zipCode" component="input" type="text"/> 
        </div>
    }
}

Trong ví dụ trên ta thấy redux form quản lí các trường là streetName, number, zipCode. Trong khi validate ta sẽ lấy được giá trị của trường này qua parameter values như sau:

const addressValidate = values => {
  let errors = {}
  if (!values.streetName) {
    errors.streetName = 'Required'
  }
  
  if (!values.number) {
    errors.number = 'Required'
  }
  
  if (!values.zipCode) {
    errors.zipCode = 'Required'
  }
  
  return errors;
}

Trong một trường hợp khác Address không phải là một form riêng biệt vậy ta phải làm thế nào. Ví dụ như trong một order sẽ có địa chỉ người nhận. Như vậy ta có quan hệ 1-1 là order có một receiver và làm thế nào để sử dụng lại được component Address. Để tạo ra form theo cấu trúc nested với quan hệ 1-1 ta dùng FormSection như sau.

Ta sẽ tạo một component Order như sau

class OrderForm extends Component {
    render() {
        return <form>
            <FormSection name="receiverAddress">
               <Address/>
            </FormSection>
        </form>
    }
}

Khi đó ta sẽ validate cho order form này như sau

const orderValidate = values => {
  let errors = {}
  let receiverAddressErrors = addressValidate(values.receiverAddress);
  if (Object.keys(receiverAddressErrors).length > 0) {
    errors.receiverAddress = receiverAddressErrors;
  }
  return errors;
}

Ta thấy việc validate nested form với quan hệ 1-1 là khá đơn giản. Ta chỉ cần đặt component trong thẻ FormSection và đặt tên quan hệ này là thuộc tính name của FormSection.

Tiếp theo là đến quan hệ 1 nhiều. Ví dụ một người dùng có nhiều thẻ tín dụng và muốn tạo ra cùng một lúc nhiều thẻ chẳng hạn. Đầu tiên ta có component CreditCard như sau

class CreditCard extends Component {
    render() {
        return <div>
            <Field name="number" component="input" type="text"/>
            <Field name="expireMonth" component="input" type="text"/> 
            <Field name="expireYear" component="input" type="text"/> 
            <Field name="securityCode" component="input" type="text"/> 
        </div>
    }
}
const creditCardValidate = values => {
  let errors = {}
  if (!values.number) {
    errors.number = "Required"
  }
  
  if (!values.expireMonth) {
    errors.expireMonth = "Required"
  }
  
   if (!values.expireYear) {
    errors.expireYear = "Required"
  }
  
   if (!values.securityCode) {
    errors.securityCode = "Required"
  }
  return errors;
}

Tiếp theo ta tạo component User trong component này có thể tạo ra nhiều CreditCard

const renderCreditCards = ({ fields, meta: { touched, error } }) => (
  <ul>
    <li>
      <button type="button" onClick={() => fields.push({})}>Add Credit Card</button>
      {touched && error && <span>{error}</span>}
    </li>
    {fields.map((creditCard, index) =>
      <li key={index}>
        <button
          type="button"
          title="Remove Credit Card"
          onClick={() => fields.remove(index)}/>
        <h4>Member #{index + 1}</h4>
	<FormSection name={creditCard}>
          <CreditCard/>
        </FormSelection>
      </li>
    )}
  </ul>
)

class User extends Component {
    render() {
        return <form>
            <FieldArray name="creditCards" component={renderCreditCards}/>
        </form>
    }
}

Qua ví dụ trên ta thấy để làm việc với quan hệ 1 nhiều ta dùng component FieldArray để tạo ra một dãy các field. Ta sẽ có validate tương ứng cho form User này như sau

const orderValidate = values => {
  let errors = {};
  let creditCardErrors = [];
  values.creditCards.forEach((creditCard, index) => {
    let creditCardErrors = creditCardValidate(creditCard);
    creditCardErrors[index] = creditCardErrors;
  });
  if (creditCardErrors.length > 0) {errors.creditCards = creditCardErrors}
  return errors;
}

Các bạn có thể thấy việc vilidate cho các kiểu quan hệ 1-1 hay 1 - nhiều theo cấu trúc nested của redux-form cũng không quá phức tạp và khá là dễ sử dụng.

2. Khởi tạo dữ liệu cho form

Để khởi tạo dữ liệu (init) cho form ta có thể dùng 1 trong 2 cách sau

  • khởi tạo dữ liệu từ state: Khởi tạo dữ liệu cho form từ state cũng khá đơn giản. Bạn chỉ việc dùng connect lấy dữ liệu của form này rồi đưa vào props initialValues là được. Dữ liệu sẽ được lấy từ state và khởi tạo cho form cấu trúc của dữ liệu sẽ giống với dữ liệu nhận được trong validate.
class User extends Component {
...
}
const mapStateToProps = state => ({
  initialValues: state.user
});

export default connect(mapStateToProps)(User);
  • Cách thứ 2 ta dùng một props khác được cung cấp bởi redux form là this.props.initialize(data) data là dữ liệu khởi tạo cho form này với cấu trúc giống với dữ liệu nhận được trong validate.

All Rights Reserved