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:
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.
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;
Advertisement
Answer
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.