I have the following component that makes a form and use formik for form validation and has a custom input field created with react-places-autocomplete for entering an address to the form. the form is working fine, However the validation is not showing even though the address field is required, When I make it empty the error validation from formik is not showing.
Below is the code for the components:
//FormikErrorLabel component
import React from 'react';
const FormikErrorLabel = ({ error, children, ...props }) => {
return <label {...props}>{children}</label>
}
export default FormikErrorLabel;
//FormikErrorLabel component
import React from 'react';
const FormikInputFeedback = ({ children }) => (
<span className="text-danger">{children}</span>
)
export default FormikInputFeedback;
//FormikPlacesAutoComplete custom input places auto complete component with formik validation
import React, { Component } from "react";
import classnames from "classnames";
import FormikErrorLabel from "./FormikErrorLabel";
import FormikInputFeedback from "./FormikInputFeedback";
import apiKey from "../../configureMap";
import Script from "react-load-script";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
const styles = {
autocompleteContainer:{
zIndex:1000
}
}
class FormikPlacesAutoComplete extends Component {
constructor(props) {
super(props);
this.state = {
address: '',
scriptLoaded:false
};
}
handleScriptLoad = () => {
this.setState({scriptLoaded:true});
};
handleChange = address => {
this.setState(()=>{
this.props.form.setFieldValue('coordinates',address)
return {address};
});
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => {
console.log('Success', latLng);
this.setState(()=>{
this.props.form.setFieldValue('coordinates',address)
return {address};
});
})
.catch(error => console.error('Error', error));
};
render() {
const {
field: { name, ...field }, // { name, value, onChange, onBlur }
form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
className,
label,
...props
} = this.props;
const error = errors[name];
const touch = touched[name];
const classes = classnames(
"form-group",
{
"animated shake error": !!error
},
className
);
console.log("props", props);
return (
<React.Fragment>
<Script
url={`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`}
onLoad={this.handleScriptLoad}
/>
{ this.state.scriptLoaded &&
<div className={classes}>
<FormikErrorLabel htmlFor={name} error={error}>
{label}
</FormikErrorLabel>
<PlacesAutocomplete
name={name}
id={name}
{...field}
{...props}
// onChange={(selectValue) => this.setState(() => {
// this.props.form.setFieldValue('categories',selectValue)
// return { selectValue }
// })}
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
// className="form-control"
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input form-control',
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
{touch && error && <FormikInputFeedback>{error}</FormikInputFeedback>}
</div>
}
</React.Fragment>
);
}
}
export default FormikPlacesAutoComplete;
//Form component
import React, { Component } from "react";
import PropTypes from "prop-types";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { actions as locationActions } from "../../duckes/locations";
import { getElementByID } from "../../utils";
import toastr from "toastr";
import { Formik, Form, Field } from 'formik'
import { object, string, array } from 'yup';
import isEmpty from 'lodash/isEmpty'
import FormikTextInput from "../common/FormikTextInput";
import FormikSelectInput from "../common/FormikSelectInput";
import FormikPlacesAutoComplete from "../common/FormikPlacesAutoComplete";
class ManageLocationPage extends Component {
render() {
})
return (
<Formik
validationSchema={object().shape({
coordinates: string()
.required('Coordinates is required.')
})}
initialValues={
{...this.props.location }
}
onSubmit={(values, actions) => {
console.log('form values:',values)
}}
render={({errors, dirty, isSubmitting, values, setFieldValue}) => (
<Form>
<h3 className="my-5 text-capitalize">Manage Location</h3>
<Field
type="text"
name="coordinates"
label="Coordinates"
component={FormikPlacesAutoComplete}
/>
<button
type="submit"
className="btn btn-default"
disabled={isSubmitting || !isEmpty(errors) || !dirty}
>
Save
</button>
</Form>
)}
/>
);
}
}
//Prop Types validation
ManageLocationPage.propTypes = {
location: PropTypes.object.isRequired,
categories: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
//Redux connect
const mapStateToProps = ({ locations, categories }, ownProps) => {
let location = {
...
coordinates: ""
};
return {
location: getElementByID(....) || location,
};
};
const mapDispatchToProps = dispatch => {
return {
actions: bindActionCreators(locationActions, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ManageLocationPage);
My code is based on react-places-autocomplete example, And this article.
How to make the formik validation errors to show?
It seems that there is something that maybe I am missing about formik validation, When I clear the address field in the GUI and debug my FormikPlacesAutoComplete onChange handler:
handleChange = address => {
this.setState(()=>{
this.props.form.setFieldValue('address',address);
this.props.form.setFieldValue('latLng',{lat:null,lng:null})
return {address};
});
};
I see that after the lines 3,4 when I check the form values in the debugger log: this.props.form.values.address = "AZ, USA" (instead of "" )
this.props.form.values.latLng = {lat: 34.0489281, lng: -111.09373110000001} (instead of {lat: null, lng: null} )
Formik doesn't react after lines 3,4 maybe I didn't completely understandd how this.props.form.setFieldValue works, I thought setFieldValue will trigger the validation, I will further investigate.
How to use React Material UI's Autocomplete component with Formik? To use React Material UI's Autocomplete component with Formik, we can call Formik's setFieldValue function with the name prop value of the Autocomplete and the value that is selected. We add the Form component with the Autocomplete component inside.
Because we ❤️ Yup sooo much, Formik has a special config option / prop for Yup object schemas called validationSchema which will automatically transform Yup's validation errors into a pretty object whose keys match values and touched .
it is very simple just do console. log(formik. values) and you will get all the values without submitting it.
To take advantage of touched , we pass formik. handleBlur to each input's onBlur prop. This function works similarly to formik. handleChange in that it uses the name attribute to figure out which field to update.
Probably late but for future reference .... ;-)
You are missing the setFieldTouched calls. ErrorMessage will only display errors when the field is touched.
I created a component like yours (partially copied some code ... CodePen Link
Still needs some work.
Currently the autocomplete field has a structure containing a value, address and lat/lng.
Onchange modifies the value (needed for required validation, hence the setTouched of location.value) OnSelect modifies the address and lat/lng. If address is empty it means the entered value doesn't match an address ... setTouched of location.address.
I suffered with this problem, but solution was pretty obvious. Places Autocomplete is like nested object, so to validate it you need to use getIn(),it is formik validation function for nested objects. and you should pass the object.value, in my case it's name.
Validation schema is like:
const validationSchema = yup.object({
legalAddress: yup.object().shape({
addressLine: yup.string().required('Required')
})
import { Form, InputGroup } from 'react-bootstrap';
import { getIn } from 'formik';
const name = legalAddress.addressLine;
<InputGroup>
<Form.Control {
...getInputProps({
placeholder: 'Search Places ...', name,
autoComplete: name + Date.now()
}
)}
isValid={
getIn(touched, name) &&
!getIn(errors, name)
}
isInvalid={
getIn(touched, name) &&
!!getIn(errors, name)
}
/>
</InputGroup>
If there will be any questions, I'll be happy to try to answer. I am not professional with formik , but anyway.
I lost a lot of time to figure this out, hope this answer will help someone in future.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With