React select编辑场景下联动城市下拉框默认值未选中问题
问题原因
核心问题是React非受控组件的defaultValue属性仅在组件首次挂载时生效一次,后续重渲染不会更新选中值。
你的代码渲染时序存在问题:
- 组件第一次挂载时,
cities初始值是空数组,城市下拉框会直接渲染,此时传入的defaultValue={city}对应的city状态是空字符串,下拉框默认选中空选项 - 之后useEffect里的异步逻辑执行完成,才会更新regions、cities、region、city这些状态触发重渲染,但此时城市下拉框已经挂载过了,
defaultValue不会再生效,所以哪怕city状态值正确、option列表也渲染完成,下拉框不会自动选中对应项 - 区域下拉框能正常选中,是因为你加了
regions && regions.length > 0的判断,等regions数据加载完成才第一次渲染区域select,首次挂载时defaultValue已经拿到了正确的region值,所以表现正常。
另外你map渲染option时,回调参数命名和外层state的region/city重名,会造成变量遮蔽,埋下隐性bug。
修复方法
两种方案选其一即可,优先推荐受控组件写法,更符合React状态驱动的设计逻辑。
方案1:改为受控组件(推荐)
把所有表单元素的defaultValue替换为value,选中值完全由state控制,状态更新时选中项会自动同步。
修正后的完整组件代码:
import React, {useState, useEffect} from "react"; export const Test = () => { const [name, setName] = useState(''); const [city, setCity] = useState(''); const [region, setRegion] = useState(''); const [regions, setRegions] = useState([]); const [cities, setCities] = useState([]); const [fetchingCities, setFetchingCities] = useState(false); const getCities = async () => { return [ {_id: 1, region: 'region1', cities: [{id: '6293609d86af877f09bb9bc1', name: 'city1'},{id: '6293609d86af877f09bb9bc2', name: 'city2'}]}, {_id: 2, region: 'region2', cities: [{id: '6293609d86af877f09bb9bc3', name: 'city3'},{id: '6293609d86af877f09bb9bc4', name: 'city4'}]} ] } const getAllCities = async () => { const all = await getCities(); setRegions(all); const form = { region: 1, city: '6293609d86af877f09bb9bc1', name: 'testname' }; if(form){ setName(form.name); const cts = all.filter(e => e._id === Number(form.region)); if(cts && cts.length && cts[0].cities){ setCities(cts[0].cities); setRegion(form.region); setCity(form.city); console.log('form.city',form.city); } } } const citySelected = (value) => { console.log("value",value); setCity(value); } const cityChanged = (value) => { console.log("cityChanged",value); const cts = regions.filter(e => e._id === Number(value)); console.log("cts",cts); if(cts && cts.length && cts[0].cities){ setCities(cts[0].cities); setRegion(value); setCity(''); // 切换区域时清空已选城市,避免值不匹配 } } useEffect(() => { if(!fetchingCities){ setFetchingCities(true); getAllCities(); } }, []); return ( <> <div> <div> <input type="text" value={name} onChange={e => setName(e.target.value)}/> </div> <div> { regions && regions.length > 0 && <select value={region} onChange={e => cityChanged(e.target.value)}> <option value=""> Select </option> {regions.map((regionItem, index) => ( <option key={index} value={regionItem._id}> {regionItem.region} </option> ))} </select> } </div> <div> { cities && cities.length > 0 && <select value={city} onChange={e => citySelected(e.target.value)}> <option value=""> Select </option> {cities.map((cityItem, index) => ( <option key={index} value={cityItem.id}> {cityItem.name} </option> ))} </select> } </div> <span>city:{city}</span> </div> </> ); };
方案2:保留非受控组件写法
如果坚持用defaultValue的非受控模式,给城市下拉框加上和区域下拉框一致的渲染判断,等cities数据、city值都初始化完成后再第一次渲染城市select,保证首次挂载时defaultValue能拿到正确的初始值:
<div> { cities && cities.length > 0 && <select defaultValue={city} onChange={e => citySelected(e.target.value)}> <option value="">Select</option> {cities.map((cityItem, index) => ( <option key={index} value={cityItem.id}> {cityItem.name} </option> ))} </select> } </div>
同样需要修正map回调的参数命名,避免变量遮蔽,切换区域时也要记得重置城市值。
内容的提问来源于stack exchange,提问作者oderfla
相关产品推荐
相关产品推荐

