7

I'm using "revalidate" to validate "redux-form" forms, but I'm facing this situation where there's a form that is generated dynamically based on an API response that I map over it and display the inputs inside the form.

Here's an example of how I normally validate "redux forms" with "revalidate ...

const validate = combineValidators({
    name: composeValidators(
        isRequired({ message: "Please enter your full name." }),
        hasLengthLessThan(255)({
            message: "Name can't exceed 255 characters."
        })
    )(),
    email: composeValidators(
        isRequired({ message: "Please enter your e-mail address." }),
        matchesPattern(IS_EMAIL)({
            message: "Please enter a valid e-mail address."
        })
    )()
});

export default compose(
    connect(
        null,
        actions
    ),
    reduxForm({ form: "signupForm", enableReinitialize: true, validate })
)(SignUpForm);

Now, how I go about doing a similar thing with the auto-generated forms?

Ruby
  • 2,207
  • 12
  • 42
  • 71
  • the second parameters of the `validate` function is the component `props`. maybe you can find there your dynamic fields and build up the validation accordingly? – stilllife Mar 18 '19 at 10:35
  • @stilllife Could you show an example of what you mean? – Ruby Mar 18 '19 at 16:03

1 Answers1

3

The idea is to get the dynamicFields from the ownProps which is the second argument of the validate function and use them as you please to populate the validation.

Since combineValidators is an high order function, after running it we call the resulting function with values as parameter.

// assuming dynamicFields an array like:
[
    {
        id: 1,
        name: 'age',
        component: 'input',
        label: 'Age',
        placeholder: 'Age'
    },
    {
        id: 2,
        name: 'size',
        component: 'input',
        label: 'Size',
        placeholder: 'Size'
    }
]
///////

const validate = (values, ownProps) => {
const staticValidations = {
    firstname: composeValidators(
        isRequired({ message: 'Please enter the first name.' }),
        hasLengthLessThan(255)({
            message: "Name can't exceed 255 characters."
        })
    )(),
    lastname: composeValidators(
        isRequired({ message: 'Please enter the lastname' }),
        matchesPattern('abc')({
            message: 'Please enter a valid lastname'
        })
    )()
}
const dynamicValidations = ownProps.dynamicFields.reduce((accu, curr) => {
    accu[curr.name] = isRequired({ message: 'Please enter ' + curr.name })
    return accu
}, {})

return combineValidators({
    ...staticValidations,
    ...dynamicValidations
})(values)

}

In this example I am just putting the isRequired but you can be more creative, e.g. passing a type to your dynamic field data and doing things like if type === ABC then do XYZ

A full codesandbox of the validation is provided here -> https://codesandbox.io/embed/py5wqpjn40?fontsize=14

stilllife
  • 1,776
  • 1
  • 18
  • 38
  • That seems logical, and I think it should work, I'll try it later, thanks, man. – Ruby Mar 25 '19 at 16:59
  • One question, the "ownProps" at the time the component renders is not available, it's not available until the request to the API completes in ComponentDidMount, how to get the "ownProps" after the request finish? – Ruby Mar 25 '19 at 17:31
  • The props are always there but your data come in later. When you store the API result in redux, you can use react-redux connect with `mapStateToProps` to make them available as I did in the codesandbox. That causes the component to re-render and so at that point the validation will be correct. If 'dynamicFields' are undefined at first render , just adapt the validation to not use `.reduce` but to just return the static ones (ignoring the dynamic ones) – stilllife Mar 25 '19 at 18:00
  • Here's what I mean, please take a look at the example I made after modifying your code ... https://codesandbox.io/s/jn1956zy1w You'll find when I try to console log the "dynamicValidations" it gives me an empty object, and the validation doesn't work. I would really appreciate your help on that one. – Ruby Mar 27 '19 at 16:14
  • As you see in the example I made, I'm using "jsonplaceholder" as an example, and I just render the dynamic inputs, I removed the static ones. – Ruby Mar 27 '19 at 16:16
  • So yes, the validate does not rerun when the component updates. It's the default behavior of redux-form. So in your case the flow is mount->validate->apiCall->apiResponse->compUpdate (no validate) and so in the console you see the empty object. As soon as you start typing the validate kicks in and the console log shows the correct property. But AFAIK this is OK, you can still render your dynamic components and as soon as you start typing the validation will work. You can eventually trigger a 'validate` programmatically if you need, but you can find out how in other docs or SO questions – stilllife Mar 28 '19 at 09:47
  • Yes, once I type the validation kicks in, but even after that, when I leave the input empty, nothing happens? and the form doesn't show any error messages. – Ruby Mar 28 '19 at 13:58
  • I see now what you mean. There seems to be an incopatility between the validation mapping and your format with arrays like `question[1], question[2]`. I have changed it to `question.1 question.2` and it works -> https://codesandbox.io/s/qqklyxk4lj . This is probably due to the way redux-form stores 'subfields' and how it maps validation rules to them. – stilllife Mar 28 '19 at 19:04
  • Yes, you're actually right, it works with the format of "question.1", that's very weird. Thanks for your help. – Ruby Mar 28 '19 at 19:41
  • Even after Validate runs for me, by entering data into the input field manually or programmatically, ```ownprops``` does not show the the from which I mapped to the component via ```mapStateToProps```. I can see my form via ```this.props```, but this not useful as I need the validate function to see the form so I can dynamically make the error messages. – Bromox May 07 '19 at 20:17
  • My form is for FirstName, LastName, but you can have many rows (up to the user). My method is, ```componentWillMount()```, creates an array with the fields properties, this is looped based on a number defined by user. This is push this array redux, I then ```mapStateToProps(state){}```. I can now see my form in this.props, however I cannot see the prop within ```validate (values, ownProps)```. Even if I trigger validate to run manually or programmatically, what I map to props does not show up in ```ownProps```. My form renders excellent but I can write any validation. – Bromox May 07 '19 at 20:54