I am building a form with react which has three fields: Make, Model and ZipCode. The zipcode field comes with a default value, so when I choose a make and a model and click the submit button, it should pull that make and model in the selected zipcode, however, the zipcode input field will tell me to enter a valid zipcode unless I click on the zipcode input before hitting submit.
Please see the image below:
A console.log
of the zipcode field will show the default zipcode is in the input, however, it will only be recognized after clicking in the input itself.
Edit: The image does not render sometimes, so please check this link: https://i.stack.imgur.com/8hfpc.png
Edit 4:
Added the complete component.
// Packages
import { useEffect, useState } from 'react';
// Definitions
import { IPlainObject } from '@/def/IPlainObject';
import { IModel } from '@/def/IModel';
// Components
import Box from '@/comp/box';
import Button from '@/comp/button';
import Input from '@/comp/form-elements/input';
import Select from '@/comp/form-elements/select';
// Contents
import { labelHelper } from '@/contents/index';
// Context
import { useAppContext } from '@/ctx/app-context';
// Services
import { getZipCodeInfo as getZipCodeInfoService, getZipCodeByIPAddress } from '@/src/services';
import { getCampaignModels } from '@/util/get-campaign-makes';
const FormOne: React.FC<IPlainObject> = (props) => {
const { buttonText, makes, campaign } = props;
const {
state: { zipCodeInfo },
setSelectedMake,
setSelectedModel,
setZipCodeInfo,
} = useAppContext();
const [cue, setCue] = useState<string>('make');
const [error, setError] = useState<string>('');
const [models, setModels] = useState<IModel[]>(props.models || []);
const [isLoading, setIsLoading] = useState<boolean>(false);
const preSelectedMakeName: string = props.preSelectedMake !== undefined ? props.preSelectedMake.seoName : '';
const preSelectedModelName: string = props.preSelectedModel !== undefined ? props.preSelectedModel.seoName : '';
useEffect(() => {
const getZipcodeIP = async () => {
const zipcodeByIPAddress = await getZipCodeByIPAddress();
setZipCodeInfo({
city: zipcodeByIPAddress.city,
state: zipcodeByIPAddress.state,
zip: zipcodeByIPAddress.code,
});
};
getZipcodeIP();
}, []);
const fields = [
{
field: 'make',
value: preSelectedMakeName,
empty: preSelectedMakeName.length !== 0 ? false : true,
error: false,
success: preSelectedMakeName.length !== 0 ? true : false,
},
{
field: 'model',
value: preSelectedModelName,
empty: preSelectedModelName.length !== 0 ? false : true,
error: false,
success: preSelectedModelName.length !== 0 ? true : false,
},
{ field: 'zip-code', value: '', empty: true, error: false, success: false },
];
const [formFields, setFormFields] = useState<object[]>(fields);
useEffect(() => {
if (props.preSelectedMake) setSelectedMake(props.preSelectedMake);
if (props.preSelectedModel) setSelectedModel(props.preSelectedModel);
}, []);
// Find next empty and update cue
const updateInputs = (doError: boolean) => {
setError('');
setCue('');
for (let i = 0; i < formFields.length; i ) {
const current = formFields[i]['field'];
const empty = formFields[i]['empty'];
const formFieldError = formFields[i]['error'];
let next = '';
i < 2 ? (next = formFields[i 1]['field']) : (next = '');
switch (true) {
case empty:
setCue(current);
if (doError) {
setError(current);
}
return;
case formFieldError:
setCue(current);
setError(current);
return;
default:
setCue(next);
}
}
};
const handleMakeChange = (makeName: string) => {
const make = makes.filter((make) => make.seoName === makeName) || [];
setSelectedMake(make.length !== 0 ? make[0] : {});
setSelectedModel({});
};
const handleModelChange = (modelName: string) => {
const model = models.filter((model) => model.seoName === modelName) || [];
setSelectedModel(model.length !== 0 ? model[0] : {});
};
const getModelsByMake = async (makeName: string) => {
const make = makes.find((make) => make.seoName === makeName);
if (makeName !== '') {
const { models } = await getCampaignModels(campaign, make);
setModels(models || []);
} else {
setModels([]);
}
};
const getZipCodeInfo = async (zipCode: string) => {
setIsLoading(true);
let zipCodeData = await getZipCodeInfoService(zipCode);
if (zipCodeData.length !== 0 && zipCodeData[0]['status'] === undefined) {
zipCodeData = zipCodeData[0].zipcodes[0];
setZipCodeInfo({
city: zipCodeData.default_city,
state: zipCodeData.state_abbreviation,
zip: zipCodeData.zipcode,
});
} else {
setZipCodeInfo({});
}
setIsLoading(false);
};
const validateDropdown = (
e: React.ChangeEvent<HTMLSelectElement>,
inputIndex: number,
changeHandler: (value: string) => void
) => {
const value = e.target.value;
const newFormFields = [...formFields];
const formFieldMake = { ...newFormFields[0] };
const formFieldModel = { ...newFormFields[1] };
changeHandler(value);
switch (true) {
case inputIndex === 0:
if (value) {
Object.assign(formFieldMake, { empty: false, error: false, value });
} else {
Object.assign(formFieldMake, { empty: true, error: false, value });
}
Object.assign(formFieldModel, { empty: true, error: false, value: '' });
newFormFields[inputIndex] = formFieldMake;
newFormFields[1] = formFieldModel;
setModels([]);
getModelsByMake(value);
break;
case inputIndex === 1:
if (value) {
Object.assign(formFieldModel, { empty: false, error: false, value });
} else {
Object.assign(formFieldModel, { empty: true, error: false, value });
}
newFormFields[inputIndex] = formFieldModel;
break;
}
setFormFields(newFormFields);
};
const setZipCode = (value: string) => {
const zipRegex = /^\d{5}$|^\d{5}$/;
const newFormFields = [...formFields];
const formField = { ...newFormFields[2] };
if (zipRegex.test(value)) {
Object.assign(formField, { empty: false, value: value });
} else {
setZipCodeInfo({});
Object.assign(formField, { empty: true, error: false, success: false, value: '' });
}
newFormFields[2] = formField;
setFormFields(newFormFields);
};
const handlerZipBlur = (e: React.FocusEvent<HTMLInputElement>) => {
setZipCode(e.target.value);
};
const validateZipCode = async (e: React.ChangeEvent<HTMLInputElement>) => {
const zipRegex = /^\d{5}$|^\d{5}$/;
const value = e.target.value;
setZipCode(value);
if (zipRegex.test(value)) getZipCodeInfo(value);
};
const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
updateInputs(true);
const errorInputs = formFields.filter((item) => item['empty'] || item['error']);
if (errorInputs.length === 0 && props.onSubmit !== undefined) {
props.onSubmit();
}
};
// Ctrl R on Firefox when the input is not empty
const valueFromReload = (value: string) => {
const zipRegex = /^\d{5}$|^\d{5}$/;
setZipCode(value);
if (zipRegex.test(value)) getZipCodeInfo(value);
};
useEffect(() => {
updateInputs(false);
}, [formFields]);
useEffect(() => {
if (formFields[2]['value'] !== '') {
if (!isLoading) {
const newFormFields = [...formFields];
const formField = { ...newFormFields[2] };
Object.assign(formField, {
error: zipCodeInfo.city === undefined,
success: zipCodeInfo.city !== undefined,
});
newFormFields[2] = formField;
setFormFields(newFormFields);
}
}
}, [zipCodeInfo]);
return (
<Box
step="1"
totalSteps="3"
title={labelHelper.staticLabel('stepOneFormTitle')}
subtitle={labelHelper.staticLabel('stepOneFormSubtitle')}
>
<Select
id="make"
initialValue={preSelectedMakeName}
name="make"
label="Make"
cue={cue === 'make'}
error={error === 'make'}
message="Select a"
options={makes}
handlerChange={(e) => validateDropdown(e, 0, handleMakeChange)}
/>
<Select
id="model"
initialValue={preSelectedModelName}
name="model"
label="Model"
cue={cue === 'model'}
error={error === 'model'}
message="Select a"
options={models}
handlerChange={(e) => validateDropdown(e, 1, handleModelChange)}
/>
<Input
id="zip-code"
name="zip-code"
label="Zip Code"
icon="#icon-location"
cue={cue === 'zip-code'}
error={error === 'zip-code'}
success={!cue && zipCodeInfo.city !== undefined}
type="tel"
message="Please enter a valid"
length={5}
value={zipCodeInfo.zip}
handlerBlur={handlerZipBlur}
handlerChange={validateZipCode}
handlerEffect={valueFromReload}
autocomplete="off"
onlyNumbers
/>
<Button disabled={isLoading} loading={isLoading} handlerClick={handleSubmit}>
{buttonText}
</Button>
</Box>
);
};
export default FormOne;
CodePudding user response:
Your problem is that initially in cost fields
you mark your zip code as empty
and update it only on the validateZipCode
that you call on the handlerChange
of the input component.
It means that until you click on the field on the zip code you never call the function and never update the fields
const.
A solution will be to call initially this function, for example in the initially useEffect
when you update zipCodeInfo
state. In this way, you update the empty
value of fields
before you click the submit
button.