React开发三级联动地址表单时useState状态不更新返回undefined求助
问题根因
country初始值为空字符串,首次渲染访问country.name直接触发undefined报错- React的setState是异步更新,调用后同步读取状态会拿到更新前的旧值
setFirstCountry的useCallback未添加依赖,始终读取初始的空国家列表- 国家选择器值绑定不匹配:Select的value绑定的是国家名称,MenuItem的value传的是国家短码,两边类型不一致导致选择异常
- 调用setCountries后立即执行初始化国家逻辑,此时countries状态还未更新,无法拿到最新数据
修复后完整代码
import React,{ useEffect , useState , useCallback } from 'react'; import { InputLabel, Select , MenuItem , Grid , Typography } from '@material-ui/core'; import { useForm, FormProvider } from 'react-hook-form'; import CityHandler from 'countrycitystatejson'; export const TempAddresForm = () => { const methods = useForm(); const [countries, setCountries] = useState([]); const [countryCode, setCountryCode] = useState(''); // 初始值改为null,避免访问name报错 const [country, setCountry] = useState(null); const [states, setStates] = useState([]); const [stateName, setStateName] = useState(''); const [cities, setCities] = useState([]); const [city, setCity] = useState(''); const fetchCountries = () => { const countryList = CityHandler.getCountries(); setCountries(countryList); // 直接用拿到的列表初始化,不用等state更新 setFirstCountry(countryList); }; const countryChangeHandler = (event) => { const tempCountryCode = event.target.value; const currentCountry = CityHandler.getCountryByShort(tempCountryCode); setCountry(currentCountry); setCountryCode(tempCountryCode); // 切换国家后清空省和市的选择 setStateName(''); setCities([]); setCity(''); fetchStates(tempCountryCode); } const fetchStates = (code) => { const stateList = CityHandler.getStatesByShort(code); setStates(stateList); // 可选:自动选中第一个省份 if(stateList.length > 0) { setStateName(stateList[0]); fetchCities(code, stateList[0]); } } const stateChangeHandler = (e) => { const tempState = e.target.value; setStateName(tempState); fetchCities(countryCode, tempState); setCity(''); } // 直接传参数避免依赖旧state const fetchCities = (code, stateName) => { const tempCities = CityHandler.getCities(code, stateName); setCities(tempCities); // 可选:自动选中第一个城市 if(tempCities.length > 0) { setCity(tempCities[0]); } }; const cityChangeHandler = (event) => { const tempCity = event.target.value; setCity(tempCity); } const setFirstCountry = useCallback( (countryList) => { if(countryList && countryList.length > 0) { const firstCountry = countryList[0]; setCountry(firstCountry); setCountryCode(firstCountry.shortName); fetchStates(firstCountry.shortName); } }, []); useEffect(() => { fetchCountries(); }, []); return ( <> <Typography variant="h6" gutterBottom>Shipping Address</Typography> <FormProvider {...methods}> <form onSubmit={() => {}}> <Grid container spacing={3}> <Grid item xs={12} sm={6}> <InputLabel>Country</InputLabel> {/* value改为绑定countryCode和MenuItem对应 */} <Select value={countryCode} fullWidth onChange={countryChangeHandler}> {countries.map((countryLoop) => ( <MenuItem key={countryLoop.shortName} id={countryLoop.shortName} value={countryLoop.shortName}> {countryLoop.name} </MenuItem> ))} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>State</InputLabel> <Select value={stateName} fullWidth onChange={stateChangeHandler}> {states.map((state, index) => { return( <MenuItem key={index} id={state} value={state}> {state} </MenuItem> ); })} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>City</InputLabel> <Select value={city} fullWidth onChange={cityChangeHandler}> {cities.map((city, index) => { return( <MenuItem key={index} id={city} value={city}> {city} </MenuItem> ); })} </Select> </Grid> </Grid> </form> </FormProvider> </> ) } export default TempAddresForm;
关键修改点
- 修正
country初始值为null,避免首次访问属性报错 - 修正国家选择器的值绑定,Select的value改为绑定
countryCode,和MenuItem的value值匹配 - 调整初始化逻辑,直接使用返回的国家列表进行初始化,不需要等待state异步更新
- 切换国家时同步清空省份、城市的已选值,避免旧值残留
- 给fetchCities增加国家短码参数,避免依赖旧的state值导致拉取错误
- 可选增加了自动选中首个省份、首个城市的逻辑,可根据需求删除
内容的提问来源于stack exchange,提问作者Salem Hussein
相关产品推荐
相关产品推荐

