Hello,
We're wondering if you had any patterns to do validation across a number of fields (other than doing form level validation). The example we're using is capturing date of birth across three fields (day, month, year). Each field has its own validation, but some (e.g. is it a valid date? are they over 18?) need to be done across all fields.
We would like to avoid doing this at the form level as we want to create a self contained re-usable component (i.e. <DateOfBirthField />)
Ideally when the parent form is submitted, all the validation needs to be triggered automatically.
Validation across fields being a concern of a parent node solves the problem, "where will the validation live?" For example, if the birthdate is invalid (e.g., oldEnough: false), where will that oldEnough validation state live? On the day, month, year fields? On all three?
The simplest answer is at a parent node. I recommend having a data structure like this:
"user": {
"birth": {
"day": 31,
"month": 7,
"year": 1990,
},
// ... etc.
}
And that way, you can make a sub-form like this:
import { Form, Control } from 'react-redux-form';
const isOver18 = (day, month, year) => { ... };
const BirthDate = ({forModel}) => (
<Form
model={`${forModel}.birth`}
component="div"
validators={{
'': ({day, month, year}) => isOver18(day, month, year),
}}
>
<Control model=".day" placeholder="Day" />
<Control model=".month" placeholder="Month" />
<Control model=".year" placeholder="Year" />
</Form>
);
export default BirthDate;
and use it anywhere like this:
import BirthDate from './path/to/BirthDate';
// in existing form
<Form model="user">
<Control model=".firstName" />
<Control model=".lastName" />
<BirthDate forModel="user" />
<button>Submit!</button>
</Form>
Alternatively, you can just have a custom component that renders all 3 fields as one single value, and then validate it just as you would validate any other individual field.
Does that solve your use-case?
We had a few problems implementing the form as you describe it.
import React from "react";
import {Form, Control, Errors} from "react-redux-form";
export default class DateOfBirthField extends React.Component {
render() {
const dateValidators = {
'isValid': (value) => value.day === '1' && value.month === '2' && value.year === '3'
}
return (
<Form
model=".dob"
component="div"
validators={{
'': dateValidators
}}>
<label>
Date Of Birth
<Control
model=".day"
placeholder="Day">
</Control>
<Control
model=".month"
placeholder="Month"/>
<Control
model=".year"
placeholder="Year"/>
<Errors
model="applicant.dob"
messages={{
'isValid': 'Not valid'
}}
show={{touched: true, focus: false}}
/>
</label>
</Form>
)
}
}
In this case we get an error Cannot read property 'day' of undefined thrown from the validator. This goes away if we set the Form model to applicant.dob.
Is it possible to use nested form with a partial model? Are we doing something wrong?
Hey @davidkpiano,
Have you had a chance to look at my follow up question above?
Is it possible to use nested form with a partial model?
Ah, unfortunately I overlooked that. At the moment, resolving models is not supported for <Form>, since it is assumed that the <Form> is the top-level. The solution to this will probably come in a later version, when <Fieldset> is finalized (which acts like a form, but without submit/etc.)
For now, I updated my answer above. You can create a higher-ordered component:
const BirthDate = ({forModel}) => (
<Form
model={`${forModel}.birth`}
// ... etc.
And then use it inside your <Form> as <BirthDate forModel="user" />. It's a _tiny_ bit more verbose, but at least you're using vanilla React to get to your solution rather than an abstraction.
Update: As of 1.4.1 you can now use <Fieldset> which is a much nicer solution to this. Read the docs here: https://davidkpiano.github.io/react-redux-form/docs/api/Fieldset.html
Could you add an example about the validation with Fieldset to the documentation? I somehow cannot get it to work.
Most helpful comment
Could you add an example about the validation with Fieldset to the documentation? I somehow cannot get it to work.