Select dropdown triggers re-validations of already filled input fields using formik and yup

Viewed 17

Currently, when I select city dropdown, it triggers a re-validation of already filled input fields.Image description here I'm using the County-State-City library to look up the states and cities in the State and City select fields.

form section using formik

import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import AdminLeftNavbar from '../../components/AdminLeftNavbar';
import UploadImageToS3 from '../../components/UploadImageS3/UploadImageToS3';
import { clearMessage, setMessage } from '../../redux/messageSlice';
import { Country, State, City } from 'country-state-city';
import {
  StyledHeaderWrapper,
  StyledMainContainer,
  StyledMainWrapper,
  StyledBreadCrumbs,
  StyledSectionTitle,
  StyledContentWrapper,
  OperatorVisibilityWrapper,
  FormWrapper,
  FormGroup,
  DetailsWrapper,
  AmenitiesParkingTypeWrapper,
  OtherInfoWrapper,
  OtherInfoHeader,
  AddressWrapper,
  AddressLocationWrapper,
  AddressLocationGrandWrapper,
  OperatorButtonWrapper,
  AddressHeader,
  CoordinatesHeader,
  AddressContentWrapper,
  OtherInfoSubWrapper,
} from './style';
import { createLocations } from '../../redux/locationsSlice';
import Loading from '../../components/Loading/Loading';
import FormikControl from '../../components/FormikComponents/FormikControl';

const NewLocations = () => {
  const { operatorId } = useParams();
  const [successful, setSuccessful] = useState(false);
  const [showProfile, setShowProfile] = useState(true);
  const operators = useSelector((state) => state.operators.data);
  const operator = operators.filter(
    (operator) => operator.id === parseInt(operatorId)
  );
  const { loading } = useSelector((state) => state.locations);
  const dispatch = useDispatch();
  const navigate = useNavigate();
  const DEFAULT_COUNTRY = 'US';

  useEffect(() => {
    dispatch(clearMessage());
  }, [dispatch]);

  const availableAmenitiesSelection = [
    'Smart Car',
    'Self Park',
    'Staffed',
    'Paved',
    'Driveaway',
    'Mobile Pass',
    'Twenty Four',
  ];
  const availableParkingType = ['Outdoor', 'Covered', 'Indoor', 'UnCovered'];
  const accessTypeOptions = [
    { key: 'Mobile Pay', value: 'mobile pay' },
    { key: 'Location Name', value: 'location name' },
  ];
  const validationTypeOptions = [
    { key: 'By Plate', value: 'By Plate' },
    { key: 'Location Name', value: 'location name' },
  ];

  const cityOption = [{ key: 'Abbeville', value: 'abbeville' }];

  const updateTimezones = Country.getCountryByCode(DEFAULT_COUNTRY).timezones;

  const initialValues = {
    name: '',
    bio: '',
    operatedBy: '',
    description: '',
    images: '',
    timeZone: '',
    amenities: [],
    parkingType: [],
    numberOfSpaces: '',
    operatorStreet: '',
    accessType: '',
    validationType: '',
    address: {
      street: '',
      country: 'United States',
      state: '',
      city: '',
      zip: '',
      latitude: '',
      longitude: '',
      entranceLatitude: '',
      entranceLongitude: '',
    },
  };
  const validationSchema = Yup.object().shape({
    name: Yup.string().required('Required!'),
    bio: Yup.string().required('Required!'),
    description: Yup.string().required('Required!'),
    operatedBy: Yup.string().required('Required!'),
    // images: Yup.string().required('Required!'),
    timeZone: Yup.string().required('Required!'),
    amenities: Yup.array().required('Required!'),
    parkingType: Yup.array().required('Required!'),
    numberOfSpaces: Yup.number()
      .required('Required!')
      .min(0, 'Number must be positive integer'),
    accessType: Yup.string().required('Required!'),
    validationType: Yup.string().required('Required!'),
    address: Yup.object().shape({
      street: Yup.string().required('Required!'),
      // state: Yup.string().required('Required!'),
      // city: Yup.string().required('Required!'),
      zip: Yup.string()
        .required('Required')
        .matches(/^[0-9]+$/, 'Must be only digits')
        .min(5, 'Must be exactly 5 digits')
        .max(5, 'Must be exactly 5 digits'),
      latitude: Yup.number().required('Required!'),
      longitude: Yup.number().required('Required!'),
      entranceLatitude: Yup.number().required('Required!'),
      entranceLongitude: Yup.number().required('Required!'),
    }),
  });

  const handleLocations = (formValue) => {
    const {
      name,
      bio,
      operatedBy,
      description,
      timeZone,
      numberOfSpaces,
      images,
      amenities,
      parkingType,
      accessType,
      validationType,
      address: {
        street,
        state,
        city,
        zip,
        latitude,
        longitude,
        entranceLatitude,
        entranceLongitude,
      },
    } = formValue;
    setSuccessful(false);
    dispatch(
      createLocations({
        operatorId,
        name,
        bio,
        description,
        operatedBy,
        timeZone,
        numberOfSpaces,
        amenities,
        parkingType,
        accessType,
        validationType,
        images,
        address: {
          street,
          state,
          city,
          zip,
          latitude,
          longitude,
          entranceLatitude,
          entranceLongitude,
        },
      })
    )
      .unwrap()
      .then(() => {
        setSuccessful(true);
        dispatch(setMessage('Signed up successfully.'));
        navigate(`/admin/operators/${operatorId}/locations`);
      })
      .catch(() => {
        setSuccessful(false);
      });
  };

  const updatedStates = () =>
    State.getStatesOfCountry(DEFAULT_COUNTRY).map((state) => ({
      label: state.name,
      value: state.isoCode,
      ...state,
    }));

  const updatedCities = (countryCode, isoCode) =>
    City.getCitiesOfState(countryCode, isoCode).map((city) => ({
      label: city.name,
      value: city.isoCode,
      ...city,
    }));

  if (loading) {
    return <Loading />;
  }

  return (
    <StyledMainContainer>
      <AdminLeftNavbar />
      <StyledMainWrapper>
        <StyledHeaderWrapper>
          <StyledSectionTitle>Location Profile</StyledSectionTitle>
          <StyledBreadCrumbs>
            <Link to="/admin/operators">Operator Listings</Link> &nbsp;/&nbsp;
            <span>General Info &nbsp;</span>
            /&nbsp;{' '}
            <Link to={`/admin/operators/${operatorId}/locations`}>
              Operator Locations
            </Link>
          </StyledBreadCrumbs>
        </StyledHeaderWrapper>
        <OperatorVisibilityWrapper>
          <p className="operator">
            Operator: <span>{operator.map((val) => val.name)}</span>
          </p>
          <div className="form-check form-switch form-check-reverse">
            <input
              className="form-check-input"
              type="checkbox"
              id="flexSwitchCheckReverse"
              onClick={() => setShowProfile(!showProfile)}
            />
            <label className="form-check-label" for="flexSwitchCheckReverse">
              Public Profile Visibility:{' '}
              <strong>{showProfile ? 'ON' : 'Hide'}</strong>
            </label>
          </div>
        </OperatorVisibilityWrapper>
        <StyledContentWrapper>
          <Formik
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={handleLocations}
          >
            {(
              // we need to use setFieldValue from Formik
              { values, setFieldValue, setValues }
            ) => (
              <Form>
                {!successful && (
                  <FormWrapper>
                    {showProfile ? (
                      <>
                        <h5>Profile</h5>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="string"
                            label="Location Name"
                            name="name"
                            className="form-control field-width"
                            placeholder="V Lots"
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="string"
                            label="Bio"
                            name="bio"
                            className="form-control field-width"
                            placeholder="EV Charging Stations, Reservation only, Crystal Mountain US"
                            autoComplete="off"
                          />
                        </FormGroup>
                      </>
                    ) : null}
                    <h5>Hero Image</h5>
                    <FormGroup>
                      <FormikControl
                        control="textarea"
                        type="text"
                        label=""
                        name="images"
                        className="form-control hero-field-width"
                        src=""
                      />
                      <Field name="images">
                        {({ form }) => (
                          <UploadImageToS3 setFieldValue={form.setFieldValue} />
                        )}
                      </Field>
                    </FormGroup>
                    <DetailsWrapper>
                      <h5>Details</h5>
                      <FormGroup>
                        <FormikControl
                          control="input"
                          type="string"
                          label="Operated By"
                          name="operatedBy"
                          className="form-control field-width"
                          placeholder="Crystal parking(Mineapolis, St.paul)"
                          autoComplete="off"
                        />
                      </FormGroup>
                      <FormGroup>
                        <FormikControl
                          control="textarea"
                          label="Description"
                          name="description"
                          className="form-control field-width"
                          placeholder="Type your Description"
                          autoComplete="off"
                        />
                      </FormGroup>
                      <AmenitiesParkingTypeWrapper>
                        <FormGroup>
                          <label htmlFor="amenities">
                            Amenities:
                            <Field
                              component="select"
                              name="amenities"
                              // You need to set the new field value
                              onChange={(evt) =>
                                setFieldValue(
                                  'amenities',
                                  [].slice
                                    .call(evt.target.selectedOptions)
                                    .map((option) => option.value)
                                )
                              }
                              multiple={true}
                            >
                              {availableAmenitiesSelection.map((amenity) => (
                                <option key={amenity} value={amenity}>
                                  {amenity}
                                </option>
                              ))}
                            </Field>
                            <ErrorMessage
                              name="amenities"
                              component="div"
                              className="alert alert-danger"
                            />
                          </label>
                        </FormGroup>
                        <FormGroup>
                          <label htmlFor="parkingType">
                            Parking Type:
                            <Field
                              component="select"
                              name="parkingType"
                              // You need to set the new field value
                              onChange={(evt) =>
                                setFieldValue(
                                  'parkingType',
                                  [].slice
                                    .call(evt.target.selectedOptions)
                                    .map((option) => option.value)
                                )
                              }
                              multiple={true}
                            >
                              {availableParkingType.map((parking) => (
                                <option key={parking} value={parking}>
                                  {parking}
                                </option>
                              ))}
                            </Field>
                            <ErrorMessage
                              name="parkingType"
                              component="div"
                              className="alert alert-danger"
                            />
                          </label>
                        </FormGroup>
                      </AmenitiesParkingTypeWrapper>
                    </DetailsWrapper>
                    <OtherInfoHeader>
                      <h5>Other Info</h5>
                    </OtherInfoHeader>

                    <OtherInfoWrapper>
                      <OtherInfoSubWrapper>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="number"
                            label="No. Of Spaces"
                            name="numberOfSpaces"
                            className="form-control field-width mb-2"
                            placeholder="20"
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="select"
                            label="Access Type"
                            name="accessType"
                            options={accessTypeOptions}
                            autoComplete="off"
                          />
                        </FormGroup>

                        <FormGroup>
                          <FormikControl
                            control="select"
                            label="Validation Type"
                            name="validationType"
                            options={validationTypeOptions}
                            autoComplete="off"
                          />
                        </FormGroup>
                      </OtherInfoSubWrapper>
                      <FormGroup>
                        <FormikControl
                          control="select"
                          label="Time Zone"
                          name="timeZone"
                          options={updateTimezones.map((timezone) => ({
                            value: timezone.zoneName,
                            key: timezone.zoneName,
                          }))}
                          placeholder="Eastern standard Time"
                        />
                      </FormGroup>
                    </OtherInfoWrapper>

                    <AddressWrapper>
                      <AddressHeader>
                        <h5>Address</h5>
                      </AddressHeader>
                      <AddressContentWrapper>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="string"
                            label="Street"
                            name="address.street"
                            className="form-control field-width mb-2"
                            placeholder="City Parking - 113 6th Ave..."
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="select"
                            label="State"
                            id="state"
                            name="address.state"
                            className="field-width"
                            options={updatedStates(
                              values.country ? values.country.value : null
                            ).map((state) => ({
                              value: state.isoCode,
                              key: state.name,
                            }))}
                            value={values.state}
                            onChange={(e) => {
                              e.preventDefault();
                              setValues(
                                { state: e.target.value, city: null },
                                false
                              );
                            }}
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="select"
                            label="City"
                            id="city"
                            name="address.city"
                            className="field-width"
                            options={
                              values.state
                                ? updatedCities(
                                    DEFAULT_COUNTRY,
                                    values.state
                                  ).map((city) => ({
                                    value: city.isoCode,
                                    key: city.name,
                                  }))
                                : cityOption
                            }
                            value={values.city}
                            onChange={(e) => {
                              e.preventDefault();
                              setFieldValue('city', e.target.value);
                            }}
                          />
                        </FormGroup>
                      </AddressContentWrapper>
                      <FormGroup>
                        <FormikControl
                          control="input"
                          type="string"
                          label="Zip Code"
                          name="address.zip"
                          className="form-control field-width"
                          placeholder="45545"
                          autoComplete="off"
                        />
                      </FormGroup>
                    </AddressWrapper>
                    <AddressLocationGrandWrapper>
                      <CoordinatesHeader>
                        <h5>Coordinates</h5>
                      </CoordinatesHeader>
                      <AddressLocationWrapper>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="number"
                            label="Latitude"
                            name="address.latitude"
                            className="form-control field-address-location"
                            placeholder="4.3566"
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="number"
                            label="Longitude"
                            name="address.longitude"
                            className="form-control field-address-location"
                            placeholder="6.779705"
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="number"
                            label="Entrance Location Latitude"
                            name="address.entranceLatitude"
                            className="form-control field-address-location"
                            placeholder="6.779701"
                            autoComplete="off"
                          />
                        </FormGroup>
                        <FormGroup>
                          <FormikControl
                            control="input"
                            type="number"
                            label="Entrance Location Longitude"
                            name="address.entranceLongitude"
                            className="form-control field-address-location"
                            placeholder="6.779701"
                            autoComplete="off"
                          />
                        </FormGroup>
                      </AddressLocationWrapper>
                    </AddressLocationGrandWrapper>
                    <OperatorButtonWrapper>
                      <button
                        type="submit"
                        className="operator-cancel"
                        onClick={() =>
                          navigate(`/admin/operators/${operatorId}/locations`)
                        }
                      >
                        Cancel
                      </button>
                      <button type="submit">Save</button>
                    </OperatorButtonWrapper>
                  </FormWrapper>
                )}
              </Form>
            )}
          </Formik>
        </StyledContentWrapper>
      </StyledMainWrapper>
    </StyledMainContainer>
  );
};

export default NewLocations;

select function

import { ErrorMessage, Field } from 'formik';
import TextError from '../TextError/TextError';

const Select = (props) => {
  const { label, name, options, ...rest } = props;
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <Field as="select" id={name} name={name} {...rest}>
        {options.map((option) => {
          return (
            <option key={option.value} value={option.value}>
              {option.key}
            </option>
          );
        })}
      </Field>
      <ErrorMessage name={name} component={TextError} />
    </div>
  );
};

export default Select;

FormikControl component

import CheckboxGroup from './CheckboxGroup/CheckboxGroup';
import DatePicker from './DatePicker/DatePicker';
import Input from './InputForm/Input';
import RadioButtons from './RadioButtons/RadioButtons';
import Select from './SelectForm/Select';
import Textarea from './Textarea/Textarea';

const FormikControl = (props) => {
  const { control, ...rest } = props;
  switch (control) {
    case 'input':
      return <Input {...rest} />;
    case 'textarea':
      return <Textarea {...rest} />;
    case 'select':
      return <Select {...rest} />;
    case 'radio':
      return <RadioButtons {...rest} />;
    case 'checkbox':
      return <CheckboxGroup {...rest} />;
    case 'date':
      return <DatePicker {...rest} />;
    default:
      return null;
  }
};

export default FormikControl;

Any idea on how I can prevent this uncontrolled event triggerring is humbly welcome.

0 Answers
Related