Skip to content
This repository has been archived by the owner on Aug 23, 2022. It is now read-only.

Pattern for validation across fields #545

Closed
ChrisSSocha opened this issue Nov 21, 2016 · 6 comments
Closed

Pattern for validation across fields #545

ChrisSSocha opened this issue Nov 21, 2016 · 6 comments
Labels

Comments

@ChrisSSocha
Copy link

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.

@davidkpiano
Copy link
Owner

davidkpiano commented Nov 21, 2016

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?

@ChrisSSocha
Copy link
Author

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?

@ChrisSSocha
Copy link
Author

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?

@davidkpiano
Copy link
Owner

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.

@davidkpiano
Copy link
Owner

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

@Bernhard10
Copy link

Could you add an example about the validation with Fieldset to the documentation? I somehow cannot get it to work.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants