Formik : Require validation only if the field is visible - reactjs

I have a form including a checkbox which if it is true causes a new field to appear.
and I would like that when the field is visible it can be considered as Required and no required if it is invisible.
Here endComment should be required only when show is true
Would you have a solution ?
The global code :
const Form = {
const [show, setShow] = useState<boolean>(props.event.endDate ? true : false);
const addEndComment = (value: boolean) => {
setShow(value);
};
const schema = yup.object().shape({
comment: yup.string().required(),
endComment: yup.string().required(),
});
return (
<>
<Formik
enableReinitialize
initialValues={{
comment: props.event.comment,
endComment: props.event.endComment,
}}
onSubmit={(values) => {
....
}}
validationSchema={schema}
>
{(formikProps) => (
<form onSubmit={formikProps.handleSubmit}>
<section>
<p>
<I18nWrapper
translateKey={'event.form-create-event.explanations'}
/>
</p>
</section>
<section>
<Form.Group controlId="eventComment">
<Form.Label>
<I18nWrapper
translateKey={'event.form-create-event.comment-label'}
/>
</Form.Label>
<Form.Control
value={formikProps.values.comment || ''}
onChange={formikProps.handleChange}
as="textarea"
rows={3}
name="comment"
isInvalid={!!formikProps.errors.comment}
/>
<Form.Control.Feedback
type="invalid"
role="alert"
aria-label="no comment"
>
<FontAwesomeIcon icon={faTimes} className="me-2" size="lg"/>
<I18nWrapper
translateKey={'reminder.modal.phone-reminder.error'}
/>
</Form.Control.Feedback>
</Form.Group>
</section>
<section>
<SimpleGrid columns={columns} rows={rows}/>
</section>
<section>
<Form.Group controlId="formBasicCheckbox">
<Form.Check
type="checkbox"
label={t('event.form-resolve-event.checkbox-label')}
checked={show}
onChange={(e) => addEndComment(e.target.checked)}
/>
</Form.Group>
</section>
{show ? (
<React.Fragment>
<section>
<I18nWrapper
translateKey={'event.form-resolve-event.comment-end-label'}
/>
<Form.Control
value={formikProps.values.endComment || ''}
onChange={formikProps.handleChange}
as="textarea"
rows={3}
name="endComment"
isInvalid={!!formikProps.errors.endComment}
/>
<Form.Control.Feedback
type="invalid"
role="alert"
aria-label="no comment"
>
<FontAwesomeIcon
icon={faTimes}
className="me-2"
size="lg"
/>
<I18nWrapper
translateKey={'reminder.modal.phone-reminder.error'}
/>
</Form.Control.Feedback>
</section>
</React.Fragment>
) : null}
<div className="text-center">
<GenericButton
label={'button'}
type="submit"
disabled={!formikProps.isValid}
/>
</div>
</form>
)}
</Formik>
</>
);
};
export default Form;

You can simply change the schema based on the show state
Example:
const schema = yup.object().shape({
comment: yup.string().required(),
endComment: show ? yup.string().required() : yup.string(),
});
OR
If you have the show state as a part of formik's state you can use formik's conditional validation, for example
const schema = yup.object().shape({
comment: yup.string().required(),
endComment: Yup.string().when('show', {
is: true,
then: Yup.string().required()
}),
});
Refer yup docs for more info

Related

why does handleSubmit in react hook useform is not being called

I am using useform hook but the handlesubmit function is not being called . here is the code:
This is the useform hook i am using
const {
register,
handleSubmit,
formState: { errors },
watch,
reset, } = useForm<SellingInvoiceClientDetails>({
resolver: yupResolver(SellingInvoiceScheme),
defaultValues: {
rib: "",
cardNumber: "",
cardType: CardType.IDENTITY_CARD,},});
The function i want to call in the hundleSubmit is the following
const addSellingInvoiceClientDetails = (
sellingInvoiceDetails: SellingInvoiceClientDetails
) => {
console.log(sellingInvoiceDetails.cardType);
props.setSelectedClient();
props.updateSellingInvoiceInfo(
sellingInvoiceDetails.cardType,
sellingInvoiceDetails.cardNumber,
sellingInvoiceDetails.rib
);
handleClose(); };
The code of the Form :
return (
<>
<Modal.Header closeButton>
<Modal.Title>
<FormattedMessage id={"client.info"} />
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit(addSellingInvoiceClientDetails)}>
<Form.Group className="mb-3">
<Form.Label>
<FormattedMessage id={"card.number"} />
</Form.Label>
<Form.Control
{...register("cardNumber")}
placeholder={intl.formatMessage({ id: "card.number" })}
/>
<Form.Text className=" text-danger">
{errors.cardNumber?.message}
</Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>
<FormattedMessage id={"card.type"} />
</Form.Label>
<Form.Check
{...register("cardType")}
type={"radio"}
label={intl.formatMessage({ id: CardType.IDENTITY_CARD })}
value={CardType.IDENTITY_CARD}
id={"identity_card"}
/>
<Form.Check
{...register("cardType")}
type={"radio"}
label={intl.formatMessage({ id: CardType.DRIVING_LICENCE })}
value={CardType.DRIVING_LICENCE}
id={"driving_licence"}
/>
<Form.Text className=" text-danger">
{errors.cardType?.message}
</Form.Text>
</Form.Group>
<Form.Group className="mb-3">
<Form.Label>RIP</Form.Label>
<input
type="text"
className="form-control"
{...register("rib")}
placeholder="XXXXXXXXXXXXX"
/>
<Form.Text className=" text-danger">
{errors.rib?.message}
</Form.Text>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
<FormattedMessage id={"cancel"} />
</Button>
<Button
type="submit"
variant="primary"
onClick={handleSubmit(addSellingInvoiceClientDetails)}
>
<FormattedMessage id={"ok"} />
</Button>
</Modal.Footer>
</>
);
the function addSellingInvoiceClientDetails is not being excuted and when i click the Ok button nothing happens altough the handleClose function called in cancel button is working just fine.
You have put the Button element out of the form.
Try to move it inside the <form> tag

React onSubmit are not triggered with some Form.Check checkboxes

I have written modal window with dynamic fields. Text input, date and radio boxes works fine, but when I`m trying to use checkbox inputs it falls.
handleSubmit does not work and not goes into method body
AnswerQuestion:
function AnswerQuestion(props) {
const {questionStore} = useContext(Context);
const dispatch = useNotification();
const question = questionStore.getActiveAnswer();
const show = props.show;
const handleClose = props.handleClose;
const handleUpdate = props.handleUpdate;
const [checkedState, setCheckedState] = useState(question.id && question.answerEntity.answerType === "CHECKBOX"
? Array(question.answerEntity.options.length).fill(false)
: []
)
useEffect(() => {
if(question.id && question.answerEntity.answerType === "CHECKBOX") {
const newCheckedState = question.answerEntity.options.map((option) => question.answerEntity.answer.includes(option));
setCheckedState(newCheckedState);
}
}, [])
const setInitialValues = () => {
if (question.id) {
return {
author: question.author.username,
question: question.question,
answerType: question.answerEntity.answerType,
options: question.answerEntity.options,
date: question.answerEntity.answerType === "DATE" && question.answerEntity.answer ? new Date(question.answerEntity.answer) : new Date(),
answer: question.answerEntity.answer ? question.answerEntity.answer : "",
};
} else {
return {
author: "",
question: "",
answerType: "",
options: "",
date: new Date(),
answer: "",
};
}
};
const schema = yup.object().shape({
author: yup.string().required(),
question: yup.string().required(),
answer: yup.string(),
answerCheckBox: yup.array(),
date: yup.date(),
});
const submit = (values) => {
questionStore
.answerActiveQuestion(question.answerEntity.answerType, values.answer, values.date)
.then(() => handleUpdate());
handleClose();
dispatch({
type: "SUCCESS",
message: "Your answer was saved.",
title: "Success"
})
}
return (
<Formik
enableReinitialize
render={(props) => {
return (
<AnswerQuestionForm
{...props}
show={show}
handleClose={handleClose}
checkedState={checkedState}
></AnswerQuestionForm>
);
}}
initialValues={setInitialValues()}
validationSchema={schema}
onSubmit={submit}
>
</Formik>
)
}
And AnswerQuestionForm:
function AnswerQuestionForm(props) {
const {
values,
errors,
touched,
handleSubmit,
handleChange,
handleClose,
setFieldValue,
setFieldTouched,
show,
checkedState,
} = props;
function insertAnswerModule() {
switch (values.answerType) {
case "DATE":
return (
<Col sm={9}>
<DatePicker
name="date"
value={Date.parse(values.date)}
selected={values.date}
className="form-control"
onChange={(e) => {
setFieldValue('date', e);
setFieldTouched('date');
}}
/>
</Col>
)
case "SINGLE_LINE_TEXT":
return (
<Col sm={9}>
<Form.Control
type="text"
name="answer"
value={values.answer}
onChange={handleChange}
isValid={touched.question && !errors.question}
isInvalid={!!errors.question}
/>
<Form.Control.Feedback type="invalid">
{errors.question}
</Form.Control.Feedback>
</Col>
)
case "MULTILINE_TEXT":
return (
<Col sm={9}>
<Form.Control as="textarea" rows={3}
type="text"
name="answer"
value={values.answer}
onChange={handleChange}
isValid={touched.question && !errors.question}
isInvalid={!!errors.question}
/>
<Form.Control.Feedback type="invalid">
{errors.question}
</Form.Control.Feedback>
</Col>
)
case "CHECKBOX":
return (
<Col sm={9}>
{values.options.map((option, id) => (
<Form.Check
id={id}
type="checkbox"
name="answerCheckBox"
label={option}
value={option}
defaultChecked={checkedState[id]}
onChange={handleChange}
/>
))}
</Col>
)
case "RADIO_BUTTON":
return (
<Col sm={9}>
{values.options.map((option) => (
<Form.Check
type="radio"
name="answer"
label={option}
value={option}
checked={option === values.answer}
onChange={handleChange}
/>
))}
</Col>
)
}
}
return (
<Modal show={show} onHide={handleClose} centered backdrop="static">
<Modal.Header closeButton>
<Modal.Title>Answer the question</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Row className="me-3 md-3 justify-content-between">
<Form.Group as={Row}>
<Form.Label column sm={3}>
From user
</Form.Label>
<Col sm={9}>
<Form.Control
type="text"
name="author"
value={values.author}
readOnly
disabled
></Form.Control>
</Col>
</Form.Group>
</Row>
<Row className="me-3 mt-3 md-3 justify-content-between">
<Form.Group as={Row}>
<Form.Label column sm={3}>
Question
</Form.Label>
<Col sm={9}>
<Form.Control
type="text"
name="question"
value={values.question}
readOnly
disabled
></Form.Control>
</Col>
</Form.Group>
</Row>
<Row className="me-3 mt-3 md-3 justify-content-between">
<Form.Group as={Row}>
<Form.Label column sm={3}></Form.Label>
{insertAnswerModule()}
</Form.Group>
</Row>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleSubmit}>
SAVE
</Button>
</Modal.Footer>
</Modal>
)
}
I would be glad to know where is error and how to solve it.
SOLUTION:
I passed answer as string[] if answerType is "CHECKBOX". It`s not allowed in HTML and i changed answer type to string and it begins to work.

react-datetime formik validation

I'm using react-datetime compenent in my react-bootstrap form. Formik with Yup is used for validation.
import React from 'react';
import { Container, Form, Button, Alert, Row, Col, InputGroup } from "react-bootstrap";
import "react-datetime/css/react-datetime.css";
import { Formik} from 'formik';
import * as Icon from 'react-bootstrap-icons';
import * as yup from "yup"
import Datetime from 'react-datetime';
import moment from 'moment';
const validDOB = function( current ){
return current.isBefore(moment());
};
const schema = yup.object().shape({
userId: yup.string().required('Please enter a valid User ID'),
userName: yup.string().required('User\'s Name cannot be empty'),
userDOB: yup.string().required('User\'s Date of Birth cannot be empty'),
});
function AddWorkload(){
return (
<>
<Container>
<Row className="justify-content-md-center">
<h3 style={{textAlign: 'center'}}>Add a New Workload</h3>
<Formik
validationSchema={schema}
onSubmit={console.log}
initialValues={{
userId: '',
userName: '',
userDOB: '',
}}
>
{({
handleSubmit,
handleChange,
handleBlur,
values,
touched,
isValid,
errors,
setFieldValue,
}) => (
<Col md lg="6">
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="userIdInput">
<Form.Label>User ID</Form.Label>
<InputGroup>
<Form.Control
placeholder="User ID"
type="text"
name="userId"
value={values.userId}
onChange={handleChange}
isInvalid={!!errors.userId}
aria-describedby="userIdHelpBlock"
/>
<Button variant="dark"><Icon.Search /> Find User</Button>
<Form.Control.Feedback type="invalid">
{errors.userId}
</Form.Control.Feedback>
</InputGroup>
<Form.Text id="userIdHelpBlock" muted>
Please click "Find User" to fill out the details.
</Form.Text>
</Form.Group>
<Form.Group controlId="userName">
<Form.Label>User Name</Form.Label>
<Form.Control
placeholder="User Name"
type="text"
name="userName"
value={values.userName}
onChange={handleChange}
isInvalid={!!errors.userName}
/>
<Form.Control.Feedback type="invalid">
{errors.userName}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="userDOB">
<Form.Label>Date of Birth</Form.Label>
<Datetime
inputProps={{
placeholder: 'DOB',
id: 'userDOB',
name: 'userDOB',
}}
dateFormat="DD-MM-YYYY"
timeFormat={false}
value={values.userDOB}
isValidDate={validDOB}
onChange={(dateFromValue) => {
setFieldValue('userDOB', dateFromValue);
}
}
isInvalid={!!errors.userDOB}
/>
<Form.Control.Feedback type="invalid">
{errors.userDOB}
</Form.Control.Feedback>
</Form.Group>
<div style={{clear: 'both'}}></div>
<br></br>
<div className='text-center'>
<Button variant="dark" type="reset">Reset Form</Button>{' '}
<Button variant="success" type="submit">Save</Button>
</div>
</Form>
</Col>
)}
</Formik>
</Row>
</Container>
</>
);
}
export default AddWorkload;
Validation for userId and userName is working properly. But I can't get the validation to work for userDOB. I also tried yup.date() but it doesn't work. react-datetime uses moment.js. So, what type to use for the schema and how to get the validation working for that compenent.
Any help would be appreciated.
Thanks
I found my issue. Its because Form.Control.Feedback doesn't correspond to the component Datetime.
So, I removed the Form.Control.Feedback and replaced that with a custom div to display the error message for the Datetime component.
<Form.Group controlId="patientDOB">
<Form.Label>Date of Birth</Form.Label>
<Datetime
inputProps={{
placeholder: 'DD-MM-YYYY',
id: 'userDOB',
name: 'userDOB',
className: formik.errors.userDOB && formik.touched.userDOB ? "form-control is-invalid": "form-control"
}}
closeOnSelect={true}
dateFormat="DD-MM-YYYY"
timeFormat={false}
value={formik.values.userDOB}
isValidDate={validDOB}
onChange={(dateFromValue) => {
formik.setFieldValue('userDOB', moment(dateFromValue).format('DD-MM-YYYY'));
}
}
renderInput={(props) => {
return <input {...props} value={(formik.values.userDOB) ? props.value : ''} />
}}
/>
{formik.errors.userDOB && formik.touched.userDOB ? (
<div className="text-danger" style={{fontSize: '0.875em'}}>{ formik.errors.userDOB }</div>
) : null}
</Form.Group>

focus of field controls with formik

I have a form using formik, comprising a date field and a text field.
when I add a date in the form the text field is automatically put in error.
I think it comes from the initavalues
initialValues={{
comment: '',
endDate: '',
}}
it checks the value of the comment field which is empty and declares it in error.
if I remove comment from initialvalues I end up with an error :
Property 'comment' does not exist on type '{ endDate: string; }'.
How to make so that the field comment is not in error when I empty it or I validate the form?
const FormTest = (props: {
mvts: Movement[];
tiersRef: string;
masterRef: string;
submitCallback: any;
}) => {
const schema = yup.object().shape({
comment: yup.string().required(),
endDate: yup.string().required(),
});
return (
<>
<Formik
initialValues={{
comment: '',
endDate: '',
}}
onSubmit={(values) => {}}
validationSchema={schema}
>
{(formikProps) => (
<form
onSubmit={formikProps.handleSubmit}
aria-label="form-add-promise"
>
<section>
<p>
<I18nWrapper
translateKey={'event.form-create-promise.explanations'}
/>
</p>
</section>
<section>
<Row>
<Form.Group controlId="datePromise" as={Row}>
<Form.Label column lg={6}>
<I18nWrapper
translateKey={'event.form-create-promise.date-promise'}
/>
</Form.Label>
<Col lg={6}>
<Form.Control
value={formikProps.values.comment}
type="date"
name="endDate"
aria-label="from date"
role="date"
onChange={formikProps.handleChange}
isInvalid={!!formikProps.errors.endDate}
/>
<Form.Control.Feedback
type="invalid"
role="alert"
aria-label="no date promise"
>
<FontAwesomeIcon
icon={faTimes}
className="me-2"
size="lg"
/>
<I18nWrapper
translateKey={'event.form-create-promise.error.date'}
/>
</Form.Control.Feedback>
</Col>
</Form.Group>
</Row>
</section>
<section>
<Form.Group controlId="eventComment">
<Form.Label>
<I18nWrapper
translateKey={'event.form-create-promise.error.comment'}
/>
</Form.Label>
<Form.Control
value={formikProps.values.comment}
onChange={formikProps.handleChange}
as="textarea"
aria-label="from textarea"
rows={3}
name="comment"
role="textbox"
isInvalid={!!formikProps.errors.comment}
/>
<Form.Control.Feedback
type="invalid"
role="alert"
aria-label="no comment"
>
<FontAwesomeIcon icon={faTimes} className="me-2" size="lg" />
<I18nWrapper
translateKey={'reminder.modal.phone-reminder.error'}
/>
</Form.Control.Feedback>
</Form.Group>
</section>
<div className="text-center">
<GenericButton
label={'event.form-create-promise.btn-creation'}
type="submit"
disabled={!formikProps.isValid}
/>
</div>
</form>
)}
</Formik>
</>
);
};
export default FormTest;

How to have react-datepicker update Formik nested object

So I set up datepicker within my form like so
<FieldArray
name="config"
render={(arrayHelpers) => (
<div>
{values.config.map((config, index) => (
<div key={index}>
...
<DatePicker
name={`config.${index}.date`}
type="date"
value={values.date}
className={
"form-control" +
(errors.date&& touched.date
? " is-invalid"
: "")
}
onChange={(e) =>
setFieldValue("date", e)
}
/>
The data is added to the state but as an additional field instead of updating the initial state within formik. It updates likes this.
{"domain_url":"","schema_name":"","name":"","config":[{"first":"","last":"","email":"","date":""}],"date":"2020-06-10T04:00:00.000Z"}
I would appreciate any ideas.
below is a new edit of the majority of the code.
the datepicker is not displaying the date within the form field but it is updating the state correctly, now I just need it to display correctly within the form and format the date to drop the string at the end
static propTypes = {
addTenant: PropTypes.func.isRequired,
};
onSubmit = (values) => {
values.preventDefault();
this.props.addTenant(values);
};
render() {
const {
domain_url,
schema_name,
name,
config,
} = this.state;
const TenantSchema = Yup.object().shape({
domain_url: Yup.string()
.max(255, "Must be shorter than 255 characters")
.required("Client URL header is required"),
schema_name: Yup.string()
.max(255, "Must be shorter than 255 characters")
.required("Client db name is required"),
name: Yup.string()
.max(255, "Must be shorter than 255 characters")
.required("Client name is required"),
});
return (
<div className={s.root}>
<Formik
initialValues={{
domain_url: "",
schema_name: "",
client_name: "",
config: [
{
date: "",
Tenant_description: "",
},
],
}}
// validationSchema={TenantSchema} this is commented off because it breaks
submittions
onSubmit={(values, { setSubmitting, resetForm }) => {
setSubmitting(true);
values.domain_url = values.domain_url + ".localhost";
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 100);
}}
>
{({
values,
errors,
status,
touched,
handleBlur,
handleChange,
isSubmitting,
setFieldValue,
handleSubmit,
props,
}) => (
<FormGroup>
<Form onSubmit={handleSubmit}>
<legend>
<strong>Create</strong> Tenant
</legend>
<FormGroup row>
<Label for="normal-field" md={4} className="text-md-right">
Show URL
</Label>
<Col md={7}>
<InputGroup>
<Field
id="appended-input"
name="domain_url"
type="text"
value={values.domain_url}
className={
"form-control" +
(errors.domain_url && touched.domain_url
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="domain_url"
component="div"
className="invalid-feedback"
/>
<InputGroupAddon addonType="append">
.localhost
</InputGroupAddon>
</InputGroup>
</Col>
</FormGroup>
<FormGroup row>
<Label for="normal-field" md={4} className="text-md-right">
Database Name
</Label>
<Col md={7}>
<Field
name="schema_name"
type="text"
className={
"form-control" +
(errors.schema_name && touched.schema_name
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="schema_name"
component="div"
className="invalid-feedback"
/>
</Col>
</FormGroup>
<FormGroup row>
<Label for="normal-field" md={4} className="text-md-right">
Name
</Label>
<Col md={7}>
<Field
name="name"
type="text"
className={
"form-control" +
(errors.name && touched.name
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="name"
component="div"
className="invalid-feedback"
/>
</Col>
</FormGroup>
<FieldArray
name="config"
render={(arrayHelpers) => (
<div>
{values.config.map((config, index) => (
<div key={index}>
<FormGroup row>
<Label
md={4}
className="text-md-right"
for="mask-date"
>
Tenant Description
</Label>
<Col md={7}>
<TextareaAutosize
rows={3}
name={`config.${index}.tenant_description`}
id="elastic-textarea"
type="text"
onReset={values.event_description}
placeholder="Quick description of tenant"
onChange={handleChange}
value={values.tenant_description}
onBlur={handleBlur}
className={
`form-control ${s.autogrow} transition-height` +
(errors.tenant_description &&
touched.tenant_description
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="tenant_description"
component="div"
className="invalid-feedback"
/>
</Col>
</FormGroup>
<FormGroup row>
<Label
for="normal-field"
md={4}
className="text-md-right"
>
Date
</Label>
<Col md={7}>
<DatePicker
name={`config[${index}]['date']`}
selected={getIn(values, `config[${index}]
['date']`) || ''}
value={getIn(values, `config[${index}]
['date']`) || ''}
onChange={(e) =>
setFieldValue(`config[${index}]['date']`, e);
}
/>
/>
<ErrorMessage
name="date"
component="div"
className="invalid-feedback"
/>
</Col>
</FormGroup>
</div>
))}
</div>
)}
/>
<div className="form-group">
<button
type="submit"
disabled={isSubmitting}
className="btn btn-primary mr-2"
>
Save Tenant
</button>
<button type="reset" className="btn btn-secondary">
Reset
</button>
</div>
</Form>
<Col md={7}>{JSON.stringify(values)}</Col>
</FormGroup>
)}
</Formik>
</div>
);
}
}
export default connect(null, { addTenant })(TenantForm);
Change your name,value and onChange as following
import { FieldArray, getIn } from 'formik';
<DatePicker
name={`config[${index}]['date']`}
selected={getIn(values, `config[${index}]['date']`) || ''}
value={getIn(values, `config[${index}]['date']`) || ''}
onChange={(e) =>
setFieldValue(`config[${index}]['date']`, e);
}
/>

Resources