React基于自定义Hook实现Select时默认值失效、选项去重异常问题求解
问题根因定位
- 自定义Hook状态污染:当前实现把「接口拉取的原始全量餐厅数据」和「筛选后用于页面渲染的列表」存在同一个状态中,当用户选中非All的菜系时,筛选结果会直接覆盖原始全量数据,后续切回All选项时,没有完整的源数据可以用来加载全量列表,导致状态更新失效。同时All分支的筛选逻辑存在设计错误:用"all"字符串去匹配餐厅的菜系字段,本身没有匹配合理性,自然无法返回正确结果。
- 下拉选项重复问题:之前尝试的去重逻辑
[...new Set(restaurant.cuisine)]是对单个餐厅的菜系字符串做字符级去重,并没有对所有餐厅的菜系值做整体去重;同时旧代码的下拉选项是从筛选后的渲染列表中生成的,一旦做了筛选,其他菜系选项就会消失,加上All选项的value是动态绑定当前选中的cuisine值,没有固定为"All",进一步导致受控select的选中状态异常。
分步修复方案
1. 重构自定义Hook的状态逻辑
将原始全量数据和筛选后的展示列表拆分为独立状态,避免互相覆盖:
// Hooks/useRestaurants.js import { useState, useEffect } from "react"; const useRestaurants = (cuisine) => { // 独立状态存原始接口返回的全量数据,不会被筛选逻辑覆盖 const [rawRestaurants, setRawRestaurants] = useState([]); // 独立状态存筛选后用于页面渲染的列表 const [displayRestaurants, setDisplayRestaurants] = useState([]); // 第一个Effect:只负责拉取接口原始数据,和筛选逻辑完全解耦 useEffect(() => { fetch("https://redi-final-restaurants.herokuapp.com/restaurants") .then((res) => res.json()) .then((result) => { setRawRestaurants(result.results); // 初始状态为All,直接把全量数据作为默认展示列表 setDisplayRestaurants(result.results); }) .catch((e) => console.log("接口请求错误", e)); }, []); // 第二个Effect:只负责根据选中的cuisine值做筛选 useEffect(() => { // 接口未返回数据时直接跳过,避免空值报错 if (!rawRestaurants.length) return; if (cuisine === "All") { // 选中All时直接展示全量原始数据,不需要做includes匹配 setDisplayRestaurants([...rawRestaurants]); } else { const filtered = rawRestaurants.filter((restaurant) => restaurant.cuisine.toLowerCase().includes(cuisine.toLowerCase()) ); setDisplayRestaurants(filtered); } }, [cuisine, rawRestaurants]); // 返回两个值:当前要展示的列表、原始全量数据(供下拉选项生成使用) return [displayRestaurants, rawRestaurants]; }; export default useRestaurants;
2. 修复App组件的传参逻辑
把Hook返回的原始全量数据传给Form组件,用来生成稳定的下拉选项,避免从筛选后的渲染列表生成选项:
// App.js import React, { useState } from "react"; import useRestaurants from "./useRestaurants"; import Form from "./Form"; import Restaurant from "./Restaurant"; import "./styles.css"; export default function App() { const [cuisine, setCuisine] = useState("All"); // 解构两个返回值:displayRestaurants是列表渲染用的,rawRestaurants是下拉选项生成用的 const [displayRestaurants, rawRestaurants] = useRestaurants(cuisine); const onChangeHandler = (e) => { setCuisine(e.target.value); }; return ( <div className="App"> <Form onChangeHandler={onChangeHandler} rawRestaurants={rawRestaurants} cuisine={cuisine} /> {displayRestaurants.map((restaurant) => ( <Restaurant restaurant={restaurant} key={restaurant.id} /> ))} </div> ); }
3. 修复Form组件的下拉选项逻辑
- 固定All选项的value为"All",不要动态绑定选中值
- 从传入的原始全量数据中,用Set自动去重所有菜系值,再渲染为option
- 给每个option绑定对应的value值,保证受控select的选中态正常匹配
// Form.js import React from "react"; const Form = ({ rawRestaurants, cuisine, onChangeHandler }) => { // 从原始全量数据中提取不重复的菜系值 const uniqueCuisines = [...new Set( rawRestaurants.map(restaurant => restaurant.cuisine) )]; return ( <select onChange={onChangeHandler} value={cuisine}> {/* 固定All选项的value为"All",不要动态绑定当前选中值 */} <option value="All">All</option> {uniqueCuisines.map((cuisineItem) => ( <option key={cuisineItem} value={cuisineItem}> {cuisineItem} </option> ))} </select> ); }; export default Form;
内容的提问来源于stack exchange,提问作者FD3
相关产品推荐
相关产品推荐

