React.js useState未更新:第4个联动下拉框始终无数据
联动下拉框第四项无数据问题排查
我实现了4个联动过滤的下拉框,第2至第4个下拉框的可选项会根据前一个的选择进行过滤。前3个下拉框工作正常,但第4个下拉框始终没有数据。所有下拉框均复用同一TypeAheadDropDown组件,代码无差异。控制台日志显示useFetch*函数已从REST接口获取到数据,dbSpots已正确赋值,但availableSpots始终未被设置。
相关代码
import React, { useState, useEffect } from "react"; import Button from "react-bootstrap/Button"; import "bootstrap/dist/css/bootstrap.min.css"; import { Table } from "react-bootstrap"; import { UseFetchCountries, UseFetchStates, UseFetchWaters, UseFetchSpots } from "./hooks/SearchBarHooks"; import TypeAheadDropDown from "./Typeaheads/TypeAheadDropDown"; const SearchBar = () => { const dbCountries = UseFetchCountries("http://localhost:3033/country"); const dbStates = UseFetchStates("http://localhost:3033/state"); const dbWaters = UseFetchWaters("http://localhost:3033/water"); const dbSpots = UseFetchSpots("http://localhost:3033/spot"); const [availableStates, setAvailableStates] = useState(dbStates); const [availableWaters, setAvailableWaters] = useState(dbWaters); const [availableSpots, setAvailableSpots] = useState(dbSpots); const [selectedCountry, setSelectedCountry] = useState({}); const [selectedState, setSelectedState] = useState({}); const [selectedWater, setSelectedWater] = useState({}); const [selectedSpot, setSelectedSpot] = useState({}); const selectCountryHandler = (country) => { setSelectedCountry(country); setAvailableStates(dbStates.filter((state) => state.country_id == country.id)); console.log("===> selectCountryHandler > availableSpots: " + JSON.stringify(availableSpots)); }; const selectStateHandler = (state) => { setSelectedState(state); setAvailableWaters(dbWaters.filter((water) => state.id == water.state_id)); console.log("===> selectStateHandler > availableSpots: " + JSON.stringify(availableSpots)); }; const selectWaterHandler = (water) => { setSelectedWater(water); console.log("===> water: " + JSON.stringify(water)); console.log("===> dbSpots: " + JSON.stringify(dbSpots)); setAvailableSpots(dbSpots.filter((spot) => spot.water_id == water.id)); console.log("===> Filtered Spots: " + JSON.stringify(dbSpots.filter((spot) => spot.water_id == water.id))); console.log("===> selectStateHandler > availableSpots: " + JSON.stringify(availableSpots)); }; const selectSpotHandler = (spot) => { setSelectedSpot(spot); }; const isButtonEnabled = () => { if (selectedCountry.id && selectedState.id && selectedWater.id && selectedSpot.id) { return "{{false}}"; } return "{{disabled}}"; }; useEffect(() => { setAvailableSpots(dbSpots.filter((spot) => spot.water_id == selectedWater.id)); }, [dbSpots, selectedWater]); return ( <div className="container-fluid h-100"> <div className="row bordered-component"> <h4>Select your filters</h4> <Table className="table search-bar-filter"> <tbody> <tr> <td className="dd-label-cell"> <h6>Country: </h6> </td> <td> <TypeAheadDropDown items={dbCountries} countrySelected={selectCountryHandler} /> </td> </tr> <tr> <td className="dd-label-cell"> <h6>State/Region: </h6> </td> <td> <TypeAheadDropDown items={availableStates} parent={selectedCountry} stateSelected={selectStateHandler} /> </td> </tr> <tr> <td className="dd-label-cell"> <h6>Water: </h6> </td> <td> <TypeAheadDropDown items={availableWaters} parent={selectedState} waterSelected={selectWaterHandler} /> </td> </tr> <tr> <td className="dd-label-cell"> <h6>Spot: </h6> </td> <td> <TypeAheadDropDown items={availableSpots} parent={selectedWater} spotSelected={selectSpotHandler} /> </td> </tr> <tr> <td> </td> <td> <Button className="float-right" variant="success" disabled={isButtonEnabled()}> Get Fishing Reports </Button> </td> </tr> </tbody> </Table> </div> </div> ); }; export default SearchBar;
问题原因及修复方案
1. 核心问题分析
- 异步数据初始化冲突:
UseFetchSpots是异步Hook,组件初始化时dbSpots可能为空,useState(dbSpots)设置的availableSpots初始值不会自动同步后续异步获取的数据。 - 状态更新异步性误解:
selectWaterHandler中调用setAvailableSpots后立刻打印availableSpots,此时打印的是旧值(React状态更新是异步的),会误判状态未更新。 - 空值过滤逻辑错误:初始
selectedWater为{},spot.water_id == undefined会导致过滤结果为空,且未做边界判断。 - 按钮状态返回值错误:
isButtonEnabled返回字符串而非布尔值,导致disabled属性失效。
2. 具体修复步骤
(1)修正useEffect过滤逻辑
添加边界判断,仅当selectedWater.id存在且dbSpots有数据时才过滤:
useEffect(() => { if (selectedWater.id && dbSpots.length) { setAvailableSpots(dbSpots.filter(spot => spot.water_id === selectedWater.id)); } else { setAvailableSpots([]); // 无选中项时清空下拉框,也可根据需求设为dbSpots } }, [dbSpots, selectedWater]);
(2)移除重复的状态设置
useEffect已监听selectedWater变化,删除selectWaterHandler中的setAvailableSpots,避免逻辑冲突:
const selectWaterHandler = (water) => { setSelectedWater(water); console.log("===> water: " + JSON.stringify(water)); console.log("===> dbSpots: " + JSON.stringify(dbSpots)); console.log("===> Filtered Spots: " + JSON.stringify(dbSpots.filter((spot) => spot.water_id == water.id))); // 移除setAvailableSpots调用 };
(3)修复按钮状态逻辑
返回布尔值而非字符串,确保disabled属性正常工作:
const isButtonEnabled = () => { return !(selectedCountry.id && selectedState.id && selectedWater.id && selectedSpot.id); };
(4)优化初始状态设置
异步Hook初始值设为空数组,避免初始过滤出错:
const [availableStates, setAvailableStates] = useState([]); const [availableWaters, setAvailableWaters] = useState([]); const [availableSpots, setAvailableSpots] = useState([]);
内容的提问来源于stack exchange,提问作者flyfisher1952
相关产品推荐
相关产品推荐

