Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS: where to put validation logic in a form with "nested" composite components?

Tags:

reactjs

I'm new to ReactJS and am unsure about the best place to put validation logic that is needed both by nested child components in my form, and the overall "parent" form component itself. Here is a over-simplified example that illustrates my question...

I have a object like this that represents a pet owner:

{
  name: 'Jon Arbuckle',
  pets: [
   { name: 'Odie', type: 'dog' },
   { name: 'Garfield', type: 'cat' }
  ]
}

I'm using a composite component called <PetOwnerForm> to render a form for editing this data. <PetOwnerForm> renders something like this:

<input type="text" value={name} />
<PetList value={petOwner.pets} />

<PetList> is a composite component that renders this:

<PetListItem value={this.props.value[i]} />  // Render this for each pet...
// buttons for adding/deleting pets

<PetListItem> renders something like this:

<input type="text" value={this.props.value.name} />
<PetTypePicker value={this.props.value.type} />

Lastly, <PetTypePicker> renders a <select> with <option>s for pet types.

<PetTypePicker> needs to know how to validate the selected type so it can display an inline error message (e.g., ensure that a value is selected).

However, <PetOwnerForm> also needs to know how to validate the pet type because it needs to know how to validate the entire object (on load, each time the form is updated, and before submitting the data back to the server). If any field is invalid, the "Save" button should be disabled.

So where, for example, should the "is a valid pet type selected?" logic go? (Bear in mind that this is a trivial example; in reality I have many fields like this and nested composite components).

The options I see so far are:

A) Replicate the validation logic for pet type (or whatever field) both in <PetOwnerForm> and <PetTypePicker>. This might just be a matter of calling the same, shared validation function in both places:

//PetOwnerForm.js:
validate(petOwnerObj) {
  Util.isPetTypeValid(petOwnerObj.pets[i]) // for each pet
  // validate the other properties in petOwnerObj...
}

//PetTypePicker.js:  
validate(petType) {
  Util.isPetTypeValid(petType)
}

B) Use custom PetOwner, Pet, and PetType models that have their own validators. This way you can always ask a model to validate itself, regardless of where it is. Maybe this would look something like this:

{
  name: { value: 'Jon Arbuckle', isValid: ()=>{...} },
  pets: [
  {
    name: { value: 'Garfield', isValid: ()=>{...} },
    type: { value: 'cat', isValid: ()=>{...} }
   },
   ...
  ]
}

C) Modify PetOwnerForm.js go recurse the pet owner object, validating each value, and setting an 'errors' property that child components can reference, resulting in an object like this:

{
  name: { value: 'Jon Arbuckle asdfasdfasdf^^', errors: ['Too many characters', 'Contains invalid character']] },
  pets: [
    {
      name: { value: '', errors: ['Required value missing'] },
      type: { value: 'tree', errors: ['Invalid pet type'] }
    },
    ...
  ]
}

Which option is recommended for React apps (or is there another option)?

like image 293
Clint Harris Avatar asked Feb 13 '26 10:02

Clint Harris


1 Answers

It's a nice elaborate question. This question is not specific to ReactJS applications. It applies to all frameworks that follow component model.

Following are my recommendations:

  • Differentiate between action driven validation and data format validation.
  • Low level components are aware of data format they accept, so they must validate for it. For example, postal-code, email, phone, SSN etc have fixed formats and their corresponding components must validate for the right input format.
  • Low level components are not aware of actions being performed on the overall data. For example, selection of pet-owner-type can be mandatory for "create" pet-owner action but can be optional for "save draft" action. So, low level components which are not aware of end action must not perform action driven validations.
  • Action driven validation must be performed by the higher level component aware of action, for example PetOwnerForm. Such validation result must be notified to low level components so that they can display appropriate errors. Every low level component must have an error state to support it.
like image 105
Bipul Avatar answered Feb 15 '26 12:02

Bipul