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

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>&nbsp;</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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 01:00:58