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> /
<span>General Info </span>
/ {' '}
<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.