React Formik + Yup, onChange touch the field - reactjs

I would like to conditionally display errors in my form.
The way formik works is that if you change one field all validations are ran and all errors returned even thought you changed just one.
I would like to display the error only if the field was TOUCHED and I would like a field to be TOUCHED onChange. The first change to the field should make it touched.
At the moment formik is touching fields just on submit. How would I be able to touch it onChange?
This is my current form:
const optionsForSelect = (collection) => {
return collection.map(item => ({
value: item.id,
label: item.name
}))
}
const validationSchema = yup.object().shape({
length: yup
.number()
.min(1, 'Length should be a positive non-zero integer')
.required(),
frame_rate: yup
.string()
.required()
})
class SpecificationsForm extends React.PureComponent {
render() {
const {
values,
handleChange,
handleInputChange,
handleSelectChange,
handleBlur,
errors,
touched
} = this.props;
const debouncedHandleChange = debounce(handleChange, 200)
console.log(errors)
console.log('TOUCHED')
console.log(touched)
return (
<div className="panel panel-default specifications-panel" id="js-turbosquid-product-specifications-panel">
<div className="panel-heading">
<a href="#" className="js-more-info" data-toggle="collapse" data-target="#specifications-panel-instructions" tabIndex="-1">
Specifications
<i className="fa fa-question-circle" />
</a>
</div>
<div className="panel-body panel-collapse collapse in" id="specification-panel-body">
<div className="panel-body-container">
<div id="specifications-panel-instructions" className="panel-instructions collapse" />
<div className="row">
<div className="col-xs-6">
<PanelInputField
label='Length'
value={ values.length }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
formName='turbosquid_product_form_length'
fieldName='length'
/>
<div className="form-field-error">{errors.length ? errors.length : "No Error"}</div>
<PanelSelectField
label='Frame Rate'
value={ values.frame_rate }
onChange={ ({value}) => handleSelectChange('frame_rate', value) }
formName='turbosquid_product_form_frame_rate'
fieldName='frame_rate'
options={ optionsForSelect(frameRateDropdownData) }
searchable={ false }
clearable={ false }
/>
</div>
<div className="col-xs-6">
<PanelCheckBox
label='Biped'
checked={ values.biped }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
fieldName='biped'
formName='turbosquid_product_form_biped'
/>
<PanelCheckBox
label='Loopable'
checked={ values.loopable }
onChange={ (e) => handleInputChange(e, debouncedHandleChange) }
fieldName='loopable'
formName='turbosquid_product_form_loopable'
/>
</div>
</div>
</div>
</div>
</div>
)
}
}
const ProductSpecificationsMotionCapturePanel = withFormik({
validationSchema,
enableReinitialize: true,
mapPropsToValues: (props) => (props),
handleInputChange: (props) => (props.handleInputChange),
handleSelectChange: (props) => (props.handleSelectChange),
})(SpecificationsForm)
export default ProductSpecificationsMotionCapturePanel

To touch a Formik field onChange, you can do this:
<Formik
initialValues={initialValues}
onSubmit={(values) => {
//submit form
}}>
{({ setFieldTouched, handleChange }) => {
return (
<Form>
<Field
name="type"
onChange={e => {
setFieldTouched('type');
handleChange(e);
}} />
</Form>
)
}}

Hi I think it's not doable onChange but you can do so when the input is blurred and you need to use the handleBlur function: onBlur={handleBlur}.
Also errors being an object you can display it only when a given [input name] has one.
Take a look at when validations are ran here in the docs: https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run

A workaround would be to use formik's method getFieldMeta and pass your field's name and call the value prop which isn't null when you type something.
errorMessage={
formikProps.getFieldMeta("username").value
? formikProps.errors.username
: ""
}

It's possible to set the touched value without invoking validation again and one can do so by using the useFormik hook available in React 18+.
import { useFormik } from "formik";
const Component = () => {
const { setFieldTouched, handleChanged } = useFormik({
validateOnChange: true,
validateOnBlur: true,
validateOnMount: true,
});
const handleInput = (e) => {
setFieldTouched(e.target.name, true, false);
handleChanged && handleChanged(e);
};
return <input name="email" onInput={handleInput} />;
};

Related

react hook form get error object after triggering validation

When using trigger() on react hook form I can't read the errors object on first attempt. I think this is because the object populates on a subsequent render.
Here is full working example: https://codesandbox.io/s/crimson-firefly-f8ulg7?file=/src/App.tsx
You can see the first time you click submit it logs an empty object and does not set focus. If you click it again then it will work as intended.
Here is example form code:
import "./styles.css";
import classNames from "classnames";
import { useForm } from "react-hook-form";
export default function App() {
const {
register,
handleSubmit,
trigger,
watch,
formState: { errors }
} = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<div className="App">
<div className="form">
<form onSubmit={handleSubmit(onSubmit)}>
<div className={"row"}>
<div className="label">Name</div>
<div
className={classNames({
input: true,
error: errors?.name !== undefined
})}
>
<input {...register("name", { required: "Name is required" })} />
</div>
</div>
<div className="row">
<div className="label">Company</div>
<div
className={classNames({
input: true,
error: errors?.company !== undefined
})}
>
<input
{...register("company", { required: "Company is required" })}
/>
</div>
</div>
<div className="row">
<div className="label">Tel</div>
<div
className={classNames({
input: true,
error: errors?.tel !== undefined
})}
>
<input
{...register("tel", { required: "Telephone is required" })}
/>
</div>
</div>
<div className="row">
<div className="label">Mobile</div>
<div
className={classNames({
input: true,
error: errors?.mobile !== undefined
})}
>
<input
{...register("mobile", { required: "Mobile is required" })}
/>
</div>
</div>
<div className="row">
<div className="label">Email</div>
<div
className={classNames({
input: true,
error: errors?.email !== undefined
})}
>
<input
{...register("email", { required: "Email is required" })}
/>
</div>
</div>
</form>
</div>
<div className="button">
<a
href="#"
onClick={() => {
trigger().then((res) => {
if (res) {
handleSubmit(onSubmit)();
} else {
let elem = errors[Object.keys(errors)[0]]
?.ref as HTMLInputElement;
elem?.focus();
// setTimeout(() => {
// (errors[Object.keys(errors)[0]]?.ref as HTMLInputElement).focus();
// }, 10);
// (errors[Object.keys(errors)[0]]?.ref as HTMLInputElement).focus();
console.log(errors);
}
});
}}
>
Submit
</a>
</div>
</div>
);
}
I tried using a timeout but it's still empty on the first attempt.
How do I trigger the forms validation and run code based on the results of the validation?
I want to know the errored fields but also have the ref that is included inside the error object.
After reviewing the comment from the other answer you can access the error object by using the getFieldState in the useForm hook and then calling it in your trigger console.log('error object', getFieldState('name').error). You can also just call console.log('field state', getFieldState('name')) to get more info for that field, including the error object.
I forked your sandbox with the updated code.
const {
register,
handleSubmit,
trigger,
getFieldState,
watch,
formState: {
errors
}
} = useForm();
<a
href = "#"
onClick = {
() => {
trigger().then((res) => {
if (res) {
handleSubmit(onSubmit)();
} else {
let elem = errors[Object.keys(errors)[0]] ?
.ref as HTMLInputElement;
elem ? .focus();
// setTimeout(() => {
// (errors[Object.keys(errors)[0]]?.ref as HTMLInputElement).focus();
// }, 10);
// (errors[Object.keys(errors)[0]]?.ref as HTMLInputElement).focus();
console.log("field state", getFieldState("name"));
console.log("error object", getFieldState("name").error);
console.log(errors);
}
});
}
} >
Submit
</a>
Works for react-hook-form version 7+
import "./styles.css";
import classNames from "classnames";
import { useForm } from "react-hook-form";
export default function App() {
const {
register,
handleSubmit,
trigger,
watch,
formState: { errors }
} = useForm();
const onSubmit = (data: any) => {
// send request
};
return (
<div className="App">
<div className="form">
<form onSubmit={handleSubmit(onSubmit)}>
// ...
</form>
</div>
// Not under the form
<button
className="button"
onClick={async () => {
// Check the validation of field
const validationResult = await trigger('name', {shouldFocus: true});
// Show the result of validation
console.log('Field name is valid? ->', validationResult);
console.log('Field name current value', getValues().name);
// Submit or not submit the form
if (validationResult) { handleSubmit(onSubmit)(); }
}}
>
Submit
</button>
</div>
);
}

How do I implement a custom handleChange function on Formik?

In an input element, handleChange function would receive the event object from the onChange event. How do I create a custom handleChange function for non-input fields like the following?
import React from 'react';
import { useFormik } from "formik";
const SomeForm = () =>
{
const { handleChange, handleSubmit, values } = useFormik({
initialValues: {
type: `company`, name: ``,
},
onSubmit: values => {
console.log(JSON.stringify(values, null, 2));
},
});
return (
<div>
<form onSubmit={ handleSubmit }>
<label>Type</label>
<ul>
<li className={ values.type === `company` && `active` }
onClick={() => handleChange(/* some custom handle change */)} >
Company
</li>
<li className={ values.type === `individual` && `active` }
onClick={() => handleChange(/* some custom handle change */)} >
Individual
</li>
</ul>
<label>Full Name</label>
<input type="text"
name="name"
value={ value.name }
onChange={ handleChange } />
</form>
</div>
)
};
export default SomeForm;
use setField('fieldName',value) method of form object provided in render props pattern of Field component.
I think this is what you're after. You can add your custom code after field.onChange(e).
// Custom field
const MyTextField = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<>
<input {...field} {...props}
onChange={e => {
// The original handler
field.onChange(e)
// Your custom code
console.log('I can do something else here.')
}}
className={ meta.error && 'is-invalid'}` } />
{meta.touched && meta.error && (
<div>{meta.error}</div>
)}
</>
);
};
And use it like so
<MyTextField name="entry" type="text" />

Redux Form - initialValues not set on page load

I am having some issues with setting the inital form field values using redux-form.
Here is the code I tried
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
firstName: null,
lastName: null,
}
}
componentDidMount() {
this.props.fetchProfile();
}
async handleChange(e) {
await this.setState({ 'initialValues': { [e.target.name] : e.target.value }});
await this.setState({ [e.target.name] : e.target.value });
}
onSubmit = (e) => {
this.props.saveProfile({
firstName: this.state.auth.firstName,
lastName: this.state.auth.lastName,
});
}
componentWillReceiveProps(nextProps) {
this.setState({
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
});
this.setState({ 'initialValues': {
firstName : nextProps.initialValues.firstName,
lastName : nextProps.initialValues.LastName,
}});
}
render() {
return (
<>
<form onSubmit={handleSubmit(this.onSubmit)}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
onChange={this.handleChange.bind(this)}
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props){
let firstName = '';
let lastName = '';
return {
userData: state.auth.userData,
initialValues:{
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
But it is not setting value in field when load. field is just empty
I guess Redux Form works little different.
You don't need to set explicit onChange handler or declare any state to hold form fields data in Redux Form. Update your code similar to below
import { Field, FieldArray, reduxForm, getFormValues, change } from 'redux-form'
const renderField = ({
input,
label,
type,
meta: { asyncValidating, touched, error }
}) => (
<div>
<label>{label}</label>
<div className={asyncValidating ? 'async-validating' : ''}>
<input {...input} type={type} placeholder={label}/>
{touched && error && <span>{error}</span>}
</div>
</div>
)
class Profile extends Component {
// No need constructor/to explicitly declare the state
componentDidMount() {
this.props.fetchProfile();
}
render() {
const { handleSubmit, pristine, submitting} = props; // You can have custom handleSubmit too
return (
<>
<form onSubmit={handleSubmit}>
<div>
<Field
name="firstName"
type="text"
component={renderField}
label="firstName"
/>
</div>
<div>
<Field
name="lastName"
type="text"
component={renderField}
label="lastName"
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Update Info
</button>
</div>
</form>
);
}
}
Profile = reduxForm({
form: 'Profile' ,
// fields,
validate,
asyncValidate,
enableReinitialize: true,
})(Profile);
function mapStateToProps(state, props) {
return {
userData: state.auth.userData,
initialValues:{ // These are the initial values. You can give any name here for testing to verify the ReduxForm working
firstName: state.auth.firstName,
lastName: state.auth.lastName,
}
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchProfile: () => dispatch(auth.actions.fetchProfile()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Profile);
The example in question and the answer would definitely help you to understand things better.

Error: Type '{ children: Element[]; }' has no properties in common with type 'IntrinsicAttributes & RefAttributes<HTMLFormElement>'

I am trying to use Formik in my react application. I have the login component written in ts. For some reason, the Form element inside Formik throws the following error:
Error TS2559 (TS) Type '{ children: Element[]; }' has no properties in
common with type 'IntrinsicAttributes &
RefAttributes'.
I wonder how I can mitigate this problem. Any solutions?
<div>
<Form></Form> // NO ERROR
<h4>Login</h4>
<Formik
initialValues={{
username: '',
password: ''
}}
validationSchema={Yup.object().shape({
username: Yup.string().required('Username is required'),
password: Yup.string().required('Password is required')
})}
onSubmit={({ username, password }, { setStatus, setSubmitting }) => {
setStatus();
authenticationService.login(username, password)
.then(
result => {
console.log(result);
console.log(typeof result);
if (result.status == '500') {
setSubmitting(false);
setStatus("Login failed.");
} else if (result.status == '200') {
if (result.data["roles"].result.includes('Admin'))
history.push('/admin/courses');
history.push('/courses');
}
},
error => {
setSubmitting(false);
setStatus(error);
}
);
}}
render={({ errors, status, touched, isSubmitting }) => (
<Form> // ERROR!!!
<div className="form-group">
<label htmlFor="username">Username</label>
<Field name="username" type="text" className={'form-control' + (errors.username && touched.username ? ' is-invalid' : '')} />
<ErrorMessage name="username" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
<ErrorMessage name="password" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary" disabled={isSubmitting}>Login</button>
{isSubmitting && <LoadingSpinner />}
</div>
{
status &&
<div className={'alert alert-danger'}>Login failed.</div>
}
</Form>
)}
/>
/>
</div>
This seems to be an issue with formik v2 (released 21 hours ago), I get the same problem with a clean CRA install of formik and it seems to have left out the types allowing <Form /> to have children.
I would recommend downgrading to v1.5.8 for now, I can confirm this will fix your issue.
When using formik I'd also recommend passing value types in, these provide a lot of type safety very easily. You can add type Values = { username: string, password: string } to the top of your file and pass that into the Formik component like <Formik<Values>
For anyone else stuck with this, it still seems to be a problem at least as of Formik v2.0.11 and probably above.
My particular problem was with the use of the withFormik HOC with the Formik's <Form> component, e.g.:
const MyForm = (props: Props) => {
const { things, from, the, hoc } = props;
// some logic
return (
<Form>
<MyCustomFormElements />
</Form
);
}
const FormikForm = withFormik({
mapPropsToValues: (props: OwnProps): OwnForm => {
return {
some: 'custom',
values: 'and things'
};
},
handleSubmit: (values, { props, setSubmitting }) => {
props.handleSubmit(values, setSubmitting);
},
enableReinitialize: true,
validateOnBlur: true,
validateOnChange: true,
validationSchema: (props: OwnProps) => formSchema(props.t),
})(MyForm);
export default FormikForm;
I had a look at the official example for the withFormik HOC here:
https://formik.org/docs/api/withFormik
and noticed that, even in the example, they're not using their own <Form /> component. The fix was to replace <Form /> with the standard html <form> like this (and remember to pass the handleSubmit prop to <form>, which is apparently what Formik's <Form /> is failing to do in this case):
const MyForm = (props: Props) => {
// notice the explicit use of handleSubmit!!
const { things, from, the, hoc, handleSubmit } = props;
// some logic
return (
<form onSubmit={handleSubmit}>
<MyCustomFormElements />
</form>
);
}
const FormikForm = withFormik({
mapPropsToValues: (props: OwnProps): OwnForm => {
return {
some: 'custom',
values: 'and things'
};
},
handleSubmit: (values, { props, setSubmitting }) => {
props.handleSubmit(values, setSubmitting);
},
enableReinitialize: true,
validateOnBlur: true,
validateOnChange: true,
validationSchema: (props: OwnProps) => formSchema(props.t),
})(MyForm);
export default FormikForm;

react-boostrap-typeahead reset with formik

I am using the AsyncTypeahead from the react-boostrap-typeahead library along with Formik. Both great little libraries.
A simplified version of my code looks like this
const SearchFilter = withFormik({
mapPropsToValues: (props) {
office: someIncomingValue || [{name: 'office1', id: 1}]
}
})(TheForm)
const TheForm = (props) => {
const {values, handleReset} = props;
return (
<form>
<AsyncTypeahead
defaultSelected={[values.office]}
options={...}
onChange={...SetNewValue...}
onSearch={...}/>
<button onClick={handleReset}
</form>
)
}
By using defaultSelected property on the AsynchTypeahead. i can set a default INITIAL value. But the issue i am having is when i click the button to handleRest, formik does its thing and reset the value back to office 1, but AsynchTypeahead doesnt have a way of manually passing a value back into it. So it does not change. I saw there is a selected prop available, but it just blows up when i try to use it.
Any input would be gerat
UPDATE:
Yes Selected is what i needed. i had to add an onInputChange property to keep the parent in sync with what was being typed.
I had the same question.
I came up with this solution:
import { Formik } from "formik";
import * as Yup from "yup";
import { Typeahead } from 'react-bootstrap-typeahead';
const AddCheckSchema = Yup.object().shape({
title: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required')
});
...
render() {
const users = [
{ id: 1, fullname: 'User #1' },
{ id: 2, fullname: 'User #2' },
{ id: 3, fullname: 'User #3' },
];
return (
<Formik
initialValues={{
title: '',
amount: 0,
actualDate: new Date()
}}
validationSchema={AddCheckSchema}
onSubmit={ this.onSubmit}
>
{({
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
}) => (
<form onSubmit={handleSubmit}>
<div className="form-group required">
<label className="control-label">Title:</label>
<Typeahead
multiple={false}
onChange={(selected) => {
const value = (selected.length > 0) ? selected[0].fullname : '';
setFieldValue('title', value);
}}
onInputChange={(text, event) => setFieldValue('title', text)}}
onBlur={(e) => setFieldTouched('title', true)}
labelKey="fullname"
options={users}
/>
<div className="text-danger">{touched.title && errors.title}</div>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary btn-lg">Add check</button>
</div>
</form>
)}
</Formik>
)
}
Of course, you can extract this complex logic (around Typeahead field) into separate component.
Reference to API.
In my case I had to opt for a simpler solution by taking a look at this example here https://ericgio.github.io/react-bootstrap-typeahead/#basic-example
export const SelectSearch = ({name, value, schema, setFieldValue, errors}) => {
const [singleSelections, setSingleSelections] = useState([]);
useEffect(() => {
if (singleSelections.length > 0) {
setFieldValue(name, singleSelections[0].value)
}
}, [singleSelections])
return (
<LabeledField name={name} schema={schema}>
<Typeahead
id="basic-typeahead-single"
multiple={false}
labelKey="label"
options={schema.choices}
onChange={setSingleSelections}
isInvalid={!!errors[name]}
placeholder="Choose a state..."
selected={singleSelections}
/>
<Form.Control.Feedback type="invalid">
{errors[name]}
</Form.Control.Feedback>
</LabeledField>
)
}
This component can then be rendered in the formik context like below
const Component = () => {
return (
<Formik
initialValues={...}
validationSchema={AddCheckSchema}
onSubmit={this.onSubmit}
>
{({
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
}) => (
<form onSubmit={handleSubmit}>
<div className="form-group required">
<label className="control-label">Title:</label>
<SelectSearch
name={"inputName"}
choices={choices}
setFieldValue={setFieldValue}
errors={errors}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary btn-lg">Submit</button>
</div>
</form>
)}
</Formik>
)
}

Resources