I have a form that has a select list of countries
and a cascading select list of regions
. When a new country
is selected, the corresponding regions
are loaded. I am trying to set the value for the region
after the the select list loads as the first value in the list. On page render I am loading the country
select list here:
useEffect(() => {
fetchCountries();
}, []);
When a new country
is selected, I am triggering the corresponding regions
to be loaded here:
useEffect(() => {
fetchRegions();
regionData.length && setInput({...input, ["region"]: regionData[0].value})
}, [input["country"]]);
This is also where I am trying to set the default region
value as the first item in the list, but the state is not being updated. Where have I gone wrong? More code below, with non-relevant code removed for brevity.
export default function Signup() {
const initialInput: SignupRequest = {
country: "US",
region: ""
};
const initialErrors: ValidationError = {
country: "",
region: ""
};
const [input, setInput] = useState<SignupRequest>(initialInput);
const [errors, setErrors] = useState<ValidationError>(initialErrors);
const [countryData, setCountryData] = useState<SelectListModel["data"]>([]);
const [countryLoaded, setCountryLoaded] = useState<boolean>(false);
const [regionData, setRegionData] = useState<SelectListModel["data"]>([]);
const [regionLoaded, setRegionLoaded] = useState<boolean>(false);
useEffect(() => {
fetchCountries();
}, []);
useEffect(() => {
fetchRegions();
regionData.length && setInput({...input, ["region"]: regionData[0].value})
}, [input["country"]]);
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
const { name, value } = event.target;
setInput({ ...input, [name]: value });
}
function handleBlur(event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) {
const { name, value } = event.target;
validateInput(name, value);
}
function validateInput(name: string, value: string | string[] | boolean) {
let result = ValidationService.ValidateInput(name, value);
setErrors({ ...errors, [name]: result });
}
function handleFormSubmit(event: React.FormEvent<HTMLFormElement>) {
// process form
}
async function fetchCountries() {
const response = await LocationService.getAllCountries();
if (response?.ok) {
const responseData = await response.json() as CountryResponse[];
const countries = LocationService.maptoCountrySelectList(responseData);
setCountryData(countries);
setCountryLoaded(true);
}
}
async function fetchRegions() {
const response = await LocationService.getRegionsByCountryCode(input["country"]);
if (response?.ok) {
const responseData = await response.json() as RegionResponse[];
const regions = LocationService.maptoRegionSelectList(responseData);
setRegionData(regions);
setRegionLoaded(true);
}
}
return (
<div className="account-content">
<form onSubmit={handleFormSubmit}>
<FormGroup addClass='form-group'>
<Label label='country' title='Country' />
{!countryLoaded
? <LoadingSpinner text="Loading..." />
: <SelectList data={countryData} name='country' value={input["country"]} addClass={countryLoaded && errors["country"] ? 'is-invalid' : ''} onChange={handleSelectChange} onBlur={handleBlur} />}
<FormError message={errors["country"]} />
</FormGroup>
<FormGroup addClass='form-group'>
<Label label='region' title='State/Region/Province' />
{!regionLoaded
? <LoadingSpinner text="Loading..." />
: <SelectList data={regionData} name='region' value={input["region"]} addClass={regionLoaded && errors["region"] ? 'is-invalid' : ''} onChange={handleSelectChange} onBlur={handleBlur} />}
<FormError message={errors["region"]} />
</FormGroup>
<Button type='submit' value='Sign Up' addClass='btn-mdBlue' />
</form>
</div>
)
}
CodePudding user response:
You need to setInput for region inside your fetchRegions function because setState is not synchonous so when you setRegion, and right after you setInput, region is still empty, on next render region gets filled with your previous setRegion but useEffect has already been run
const fetchRegions = async () => {
const regions = await // fetchsomething
setRegions(regions);
setInput({ ...input, region: regions[0].value });
}