Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ant design date and time pickers do not pass value through Formik (react)

I'm currently working on a booking form which is in React using Formik. I've also incorporated Ant Design's Date Picker and Time Picker for the booking date and time respectively, but I'm having difficulties getting the values to be passed back to the component.

Here is how I've set it up in the form component (I've omitted the other unrelated fields):

const { booking, handleSubmit, mode } = this.props;

...

<Formik
    initialValues={booking}
    onSubmit={handleSubmit}
    render={({errors, touched, isSubmitting}) => (
        <Form>
        ...
<div className="form-group col-sm-4 col-md-6 col-lg-4">
    <label htmlFor="booking_date">
        Booking Date <span className="required">*</span>
    </label>
    <DatePicker onChange={ (date, dateString) => setFieldValue('booking_date', dateString)} defaultValue={this.state.bookingDate}
        className="form-control" format={this.state.dateFormat} />
</div>
<div className="form-group col-sm-4 col-md-6 col-lg-4">
    <label htmlFor="start_time">
        Start Time <span className="required">*</span>
    </label>
    <TimePicker
        defaultValue={this.state.startTime}
        format={this.state.timeFormat}
        className="form-control"
        onChange={this.handleStartTimeChange}
        minuteStep={5}
        id="start_time"
        name="start_time"
    />
</div>

This is the function that handles the time change (just a state set):

handleStartTimeChange(time) {
    this.setState({
        startTime: time
    });
}

And then on the parent, the component is set up like so:

<BookingForm
    show={true}
    booking={null}
    handleSubmit={this.saveBooking.bind(this)}
    mode="add"
/>

And the saveBooking function simply console logs the params out. However, it only ever logs out the other fields such as firstname, surname and email. The dates are completely overlooked and I don't know how to be able to get the form to recognise them - I even tried creating a Formik hidden field to replicate the date value when submit but it still ignores it. The field name and ID are correct, and correlate with the database as do all the others - so I don't understand why it won't read that data?

like image 987
Michael Emerson Avatar asked Dec 13 '22 13:12

Michael Emerson


2 Answers

Put simply, you'll need to utilize Ant Design's Form.Item inside of a Formik Field's component prop.

You'll be able to add other Antd form items as well, however, there are a few quirks. As such, I'd only recommend using one or the other (not both).

Working example: https://codesandbox.io/s/4x47oznvvx

components/AntFields.js (the reason behind creating two different onChange functions is because one of the ant components passes back an event (event.target.value) while the other passes back a value -- unfortunately, a quirk when using Formik with Antd)

import map from "lodash/map";
import React from "react";
import { DatePicker, Form, Input, TimePicker, Select } from "antd";

const FormItem = Form.Item;
const { Option } = Select;

const CreateAntField = Component => ({
  field,
  form,
  hasFeedback,
  label,
  selectOptions,
  submitCount,
  type,
  ...props
}) => {
  const touched = form.touched[field.name];
  const submitted = submitCount > 0;
  const hasError = form.errors[field.name];
  const submittedError = hasError && submitted;
  const touchedError = hasError && touched;
  const onInputChange = ({ target: { value } }) =>
    form.setFieldValue(field.name, value);
  const onChange = value => form.setFieldValue(field.name, value);
  const onBlur = () => form.setFieldTouched(field.name, true);
  return (
    <div className="field-container">
      <FormItem
        label={label}
        hasFeedback={
          (hasFeedback && submitted) || (hasFeedback && touched) ? true : false
        }
        help={submittedError || touchedError ? hasError : false}
        validateStatus={submittedError || touchedError ? "error" : "success"}
      >
        <Component
          {...field}
          {...props}
          onBlur={onBlur}
          onChange={type ? onInputChange : onChange}
        >
          {selectOptions &&
            map(selectOptions, name => <Option key={name}>{name}</Option>)}
        </Component>
      </FormItem>
    </div>
  );
};

export const AntSelect = CreateAntField(Select);
export const AntDatePicker = CreateAntField(DatePicker);
export const AntInput = CreateAntField(Input);
export const AntTimePicker = CreateAntField(TimePicker);

components/FieldFormats.js

export const dateFormat = "MM-DD-YYYY";
export const timeFormat = "HH:mm";

components/ValidateFields.js

import moment from "moment";
import { dateFormat } from "./FieldFormats";

export const validateDate = value => {
  let errors;

  if (!value) {
    errors = "Required!";
  } else if (
    moment(value).format(dateFormat) < moment(Date.now()).format(dateFormat)
  ) {
    errors = "Invalid date!";
  }

  return errors;
};

export const validateEmail = value => {
  let errors;

  if (!value) {
    errors = "Required!";
  } else if (!/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
    errors = "Invalid email address!";
  }

  return errors;
};

export const isRequired = value => (!value ? "Required!" : "");

components/RenderBookingForm.js

import React from "react";
import { Form, Field } from "formik";
import { AntDatePicker, AntInput, AntSelect, AntTimePicker } from "./AntFields";
import { dateFormat, timeFormat } from "./FieldFormats";
import { validateDate, validateEmail, isRequired } from "./ValidateFields";

export default ({ handleSubmit, values, submitCount }) => (
  <Form className="form-container" onSubmit={handleSubmit}>
    <Field
      component={AntInput}
      name="email"
      type="email"
      label="Email"
      validate={validateEmail}
      submitCount={submitCount}
      hasFeedback
    />
    <Field
      component={AntDatePicker}
      name="bookingDate"
      label="Booking Date"
      defaultValue={values.bookingDate}
      format={dateFormat}
      validate={validateDate}
      submitCount={submitCount}
      hasFeedback
    />
    <Field
      component={AntTimePicker}
      name="bookingTime"
      label="Booking Time"
      defaultValue={values.bookingTime}
      format={timeFormat}
      hourStep={1}
      minuteStep={5}
      validate={isRequired}
      submitCount={submitCount}
      hasFeedback
    />
    <Field
      component={AntSelect}
      name="bookingClient"
      label="Client"
      defaultValue={values.bookingClient}
      selectOptions={values.selectOptions}
      validate={isRequired}
      submitCount={submitCount}
      tokenSeparators={[","]}
      style={{ width: 200 }}
      hasFeedback
    />
    <div className="submit-container">
      <button className="ant-btn ant-btn-primary" type="submit">
        Submit
      </button>
    </div>
  </Form>
);

components/BookingForm.js

import React, { PureComponent } from "react";
import { Formik } from "formik";
import RenderBookingForm from "./RenderBookingForm";
import { dateFormat, timeFormat } from "./FieldFormats";
import moment from "moment";

const initialValues = {
  bookingClient: "",
  bookingDate: moment(Date.now()),
  bookingTime: moment(Date.now()),
  selectOptions: ["Mark", "Bob", "Anthony"]
};

const handleSubmit = formProps => {
  const { bookingClient, bookingDate, bookingTime, email } = formProps;
  const selectedDate = moment(bookingDate).format(dateFormat);
  const selectedTime = moment(bookingTime).format(timeFormat);
  alert(
    `Email: ${email} \nSelected Date: ${selectedDate} \nSelected Time: ${selectedTime}\nSelected Client: ${bookingClient}`
  );
};

export default () => (
  <Formik
    initialValues={initialValues}
    onSubmit={handleSubmit}
    render={RenderBookingForm}
  />
);
like image 61
Matt Carlotta Avatar answered Jan 13 '23 09:01

Matt Carlotta


I don't understand why it won't read that data?

Formik passes values as values prop, they are updated using setFieldValue. When you store values in the state, Formik does't know anything about it.

Of course there is nothing wrong in storing values to the state (assuming it works) but you have to define internal submit handler to attach these values to others. By simple calling prop:

onSubmit={handleSubmit}

you have no chance to do that. Only Formik handled values will be passed. You need to define internal submit handler, sth like:

const handleSubmit = values => {
  // init with other Formik fields
  let preparedValues = { ...values }; 

  // values from state
  const { startTime, startDate } = this.state; 

  // attach directly or format with moment
  preparedValues["startTime"] = startTime;
  preparedValues["startDate"] = startDate;

  // of course w/o formatting it can be done shorter
  // let preparedValues = { ...values, ...this.state }; 

  console.log(preparedValues);

  // call external handler with all values
  this.prop.handleSubmit( preparedValues );
}
like image 23
xadm Avatar answered Jan 13 '23 10:01

xadm