Field level validation & warning
Summary
Since v7.0.0 the behaviour of Field level validation changed/was fixed, but had a significant side effect. It has caused a lot of ongoing confusion, and this was/is at least a documentation issue. This ticket is for centralizing any further discussion around this issue. Everything in this ticket applies to field level warnings as well. FieldArray lags a little, see #4081.
What is the history?
In v6.8.0 and prior component field level validation used the initial value of the validate prop provided only. After a Field mounted its validation behaviour could not be changed.
Issue #3012 and subsequent pull request #3094 addressed this in v7.0.0.
#3094 was a fix, which happened to use re-registering as it's mechanism. From v7.0.0 onwards a change to the validate prop would apply during subsequent sync validations. From v7.4.2 this was updated to run a sync validation immediately upon the detection of a new validate.
A broken pattern
If you never needed to change the validate prop after first render then before #3012 you could usefully use a higher-order-function inline to generate the validate function. After this change, unless memoized, the use of a higher order function to generate the validation function inside the render will cause the Field to re-register / re-run validations. Example:
<Field
component="input"
name={props.name}
validate={getMax(props.max)} // never do this in v7.0.0 and above (unless getMax is memoized)
>How to field level validate your Field:
The golden rule is don't change (according to ===) the validate prop unless you intend to between renders.
All of the following equally applies for passing an array e.g. [maxAge, isRequired]
# 1. If you have a fixed validation function for the whole lifecycle of your Field (trivial case):
Just make sure that your validation function is not created inside the render. E.g.
// static definition outside the Field.
const minAge = value => (value < 18) ? 'Too young' : undefined
const Age = ({ name }) => {
// do not create minAge here.
return (
<Field
component="input"
type="number"
name={name}
validate={minAge}
/>
)
}# 2. If you have a validation function that changes while your Field is mounted:
Since v7.0.0 this is now possible. Just like above, but with e.g.
validate={this.state.applyValidator ? minAge : undefined} # 3. If you need to generate a validation function based on props when rendering your Field component that will change as your component receives new props:
class MyFieldCreator extends Component {
validate = (...args) => {
return getMax(this.props.max)(...args)
}
render() {
<Field
component="input"
name={this.props.name}
validate={this.validate}
>
}
}Memoization can often be used instead of any of the above options. If you are doing more complex memoization involving closures, be sure to create a new memoizer per usage to ensure that no stale closure values are passed between components. The same applies if you're using e.g. memoize-one a new memoized validator needs to be created per component.
Other issues
Disagreement
The react docs accept the use of the inline approach when it comes to simple cases like buttons. In saying that it was a pretty big change in 6.8.0 that affected a lot of developers (possible semvar mixup, though it was fixing a bug of how it was supposed to behave). It's been expressed that using higher order functions inline is extremely desirable. The answer to this is sorry but the need to fix #3012 is more important/meaningful.
Looking forwards (a hoped for enhancement):
Feature request to those interested [Edit: as per #3724,#2881] is that this whole business would be a lot easier if the inline validation provided fieldProps as another param (value, allValues, formProps, name, fieldProps), even if it this is getting a tad overcrowded. This would allow you to skip all the constructor & componentWillReceiveProps aspects and just:
const minAge = (value, allValues, formProps, name, fieldProps) => {
const { min } = fieldProps
if (value < min) {
return 'Too young'
}
}
<Field
component="input"
type="number"
name={name}
min={min}
validate={minAge}
>The "above the Field" approaches noted above would still apply in some cases, e.g. building different arrays of validators depending on a prop like field type (i.e. the component is not known in advance).
Source: redux-form/redux-form