2
votes

I am using redux-form for a user registration form.

For the Date of Birth section of the form, I have a <select> field for day, month, and year.

So far, I have been using the Fields component of redux-form to capture each of the day, month, and year as separate variables:

const dobSelects = fields => {
  const { label, input } = fields;
  return (
    <div>
      <label>{label}</label>
      <select {...fields.day.input} name="dob" id="">
        <option />
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <select {...fields.month.input} id="">
        <option />
        <option value="january">January</option>
        <option value="february">February</option>
        <option value="march">March</option>
      </select>
      <select {...fields.year.input} id="">
        <option />
        <option value="2000">2000</option>
        <option value="2001">2001</option>
        <option value="2002">2002</option>
      </select>
    </div>
  );
};

This yields the following into my values to be submitted:

{day: "2", month: "january", year: "2002"}

However, what if I want to send one field as a Date object as opposed to three separate fields, while maintaining the same user interface?

I want the user to select the three fields, and instead of sending three separate fields, one dateOfBirth field as a Date object gets sent instead.

Furthermore, the Fields component of redux-form does not seem sufficient in this case because I cannot pass it a validate prop to validate the date of birth; I need to validate the three fields as one rather than separately.

How is this possible to achieve with redux-form?

1

1 Answers

4
votes

Instead of using the Fields component create a custom field. Here is the general idea:

class DOBSelects extends React.Component {
    constructor(props) {
        super(props);
        this.state = this.buildState(props.input.value)
    }

    componentWillReceiveProps(props) {
        if(props.input.value !== this.props.input.value) {
            this.setState(this.buildState(props.input.value));
        }
    }

    buildState(value) {
        return value ? { 
            date: value.getDate(), 
            month: value.getMonth() + 1, year: 
            value.getFullYear()
        } : {
            date: '',
            month: '',
            year: ''
        }
    }

    update(state) {
        this.setState(state);

        const { date, month, year } = { ...this.state, ...state };
        const { onChange, value } = this.props.input;

        if(date && month && year) {
            onChange(new Date(year, month - 1, date));
        } else if(value) {
            onChange(null);
        }
    }

    render () {
        const { label, input: { name } } = this.props;
        const { date, month, year } = this.state
        const thisYear = new Date().getFullYear();

        return (
        <div>
            <label htmlFor={name + '-day'}>
                {label}
            </label>
            <select onChange={e => { this.update({ date: e.target.value }); }} value={date} name={name + '-day'}>
                <option />
                {getOptions(1, 31)}
            </select>
            <select onChange={e => this.update({ month: e.target.value })} value={month} name={name + '-month'}>
                <option />
                {getOptions(1, 12)}
            </select>
            <select onChange={e => this.update({ year: e.target.value })} value={year} name={name + '-year'}>
                <option />
                {getOptions(thisYear - 20, thisYear)}
            </select>
        </div>
    );
  };
}

function getOptions(start, end) {
    const options = [];

    for(let i = start; i <= end; i++) {
        options.push(<option key={i}>{i}</option>)
    }

    return options;
}

An even simpler solution would be to use something like react-datepicker instead.