Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formik validation not working for my custom react-places-autocomplete component

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.

like image 376
Kamran Avatar asked Jul 12 '18 12:07

Kamran


People also ask

How do I use autocomplete with Formik?

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.

What is validationSchema in Formik?

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 .

How do I get Formik values before submitting?

it is very simple just do console. log(formik. values) and you will get all the values without submitting it.

What does Formik handleBlur do?

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.


2 Answers

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.

like image 163
Yoeri Avatar answered Oct 20 '22 05:10

Yoeri


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.

like image 40
Davit Mkrtchyan Avatar answered Oct 20 '22 05:10

Davit Mkrtchyan