You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 01:27:33