Ant design date and time pickers do not pass value through Formik (react)
Solution 1
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}
/>
);
Solution 2
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 );
}
Related videos on Youtube
Michael Emerson
Symfony2 and PHP developer at Media Orb in Bridgwater. Interested in photography and also code in my spare time :)
Updated on June 04, 2022Comments
-
Michael Emerson over 1 year
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 asfirstname
,surname
andemail
. 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? -
Michael Emerson almost 5 yearsI know this was answered a while ago and it does work - but I have a question. I need to pass data through to the rendered form (namely a list of clients to be chosen from a select box) but since the RenderBookingForm is just an exported constant, how can I pass this data through as a prop and then reference it in the RenderBookingForm component? Is this possible?
-
Matt Carlotta almost 5 yearsSee updated answer above (codesandbox is also updated). Just note that you'll have to come up with a strategy to create
selectOptions
. Whether it's by makingBookingForm
astateful component
that conditionally renders (reactjs.org/docs/conditional-rendering.html) a spinner while it fetches a client list in itscomponentDidMount
method from an API. This API client list is then set tostate
, which then gets passed down to the form. Or, by settingselectOptions
as a static array of strings (as shown above). Either way, it needs to be structured like:[ "opt1", "opt2" ... ]
-
Michael Emerson almost 5 yearsAh, I didn't think of passing it through to the initialValues! Yes, I will be retrieving the clients from an API call, so I just need to convert to an array instead of the object I would get normally, thank you!