React中使用useState筛选数据时出现重渲染问题
React useState筛选功能重渲染问题解决
我在App.js中尝试使用useState Hook修改HouseDetails数据以实现筛选功能,但始终出现重渲染问题。以下是App.js和Card.js的代码,即使已做相关尝试,该问题仍未解决。
App.js 原代码
import "./App.css"; import Card from "./components/Card"; import NavBar from "./components/NavBar"; import Header from "./components/Header"; import { HiChevronDown } from "react-icons/hi"; import { useState, useMemo } from "react"; import HouseDetails from "./components/RealState/HouseDetails"; function App() { const [items, setItems] = useState(HouseDetails); const filterHouse = (e) => { const newHouse = e.filter((f) => { f.name.includes('Collsmith'); return 1; }) // dont call setItems inside useMemo // it triggers another render // which triggers another useMemo call // setItems(newHouse); } const updatedItems = useMemo(() => filterHouse(items), [items]); setItems(updatedItems); return ( <div className="w-full justify-center"> <Header /> <main className="w-[76%] m-auto"> <div className="my-14 py-2"> <div className="sm:flex justify-between items-center"> <ul> <li className="hidden sm:block lg:text-4xl font-semibold"> Search properties to rent </li> </ul> <ul className="flex items-center justify-between border-2 border-gray-300 px-3 py-2 rounded-md text-gray-600"> <li className="font-semibold"> <input type="search" className="outline-none" placeholder="Search with Search Bar" /> </li> <li> <HiChevronDown /> </li> </ul> </div> <NavBar /> </div> {/* main-content */} <div className="grid xl:grid-cols-3 lg:grid-row-3 md:grid-cols-2 lg:gap-8 md:gap-6 sm:gap-4"> <Card items={items} /> </div> </main> </div> ); } export default App;
Card.js 原代码
import React, { useState, useMemo } from "react"; import { FiHeart } from "react-icons/fi"; import { MdBed } from "react-icons/md"; import { BiBath, BiArea } from "react-icons/bi"; const Card = ({ items }) => { return items && items.map((elem) => { const { id, image, name, location, price, beds, bathrooms, area } = elem; return ( <article key={id} className="flex-col justify-center rounded-lg shadow-md max-w-xl"> <div className="rounded-lg"> <img src={require('./house1.jpg')} alt="" className="rounded-lg h-72 w-full" /> </div> <div className="flex-col justify-center px-4 py-2 space-y-3"> <div className="flex-col justify-center space-y-1"> <ul className="flex justify-between items-center"> <li className="text-purple-600 font-bold text-2xl"> ${price} <span className="text-sm text-gray-400 font-semibold"> /month </span> </li> <li className="cursor-pointer p-2 rounded-full border-2 border-gray-300 text-xl text-purple-600"> <FiHeart /> </li> </ul> <ul> <li className="font-bold text-gray-600 text-2xl">{name}</li> </ul> <ul> <li className="text-gray-400 text-sm">{location}</li> </ul> </div> <div className="flex justify-between items-center py-3"> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <MdBed /> </li> <li className="text-xs text-gray-400 font-semibold"> {beds} Beds </li> </ul> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <BiBath /> </li> <li className="text-xs text-gray-400 font-semibold"> {bathrooms} Bathrooms </li> </ul> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <BiArea /> </li> <li className="text-xs text-gray-400 font-semibold"> {area} m2 </li> </ul> </div> </div> </article> ); }); }; export default Card;
问题根源分析
- 无限重渲染循环:直接在组件顶层调用
setItems(updatedItems),每次组件渲染都会触发state更新,更新后又会触发新的渲染,形成无限循环。 - 筛选逻辑错误:
filterHouse里的filter回调没有正确返回判断结果,当前写法不管f.name.includes('Collsmith')是否为真,都会返回1(等同于true),等于没有筛选效果。 - useMemo误用:useMemo的作用是缓存计算结果,避免重复计算,但你用它来触发筛选然后直接更新state,完全违背了它的使用场景。
修复方案
- 分离原始数据与筛选结果:保留原始数据的副本,避免筛选后丢失原始数据,用单独的state存储筛选结果。
- 正确实现筛选函数:在筛选回调中直接返回判断条件,确保筛选逻辑生效。
- 在合适时机触发筛选:绑定搜索框的
onChange事件,实时触发筛选,而不是在组件渲染时自动触发。 - 优化组件重渲染:用
React.memo包裹Card组件,避免props未变化时的不必要重渲染。
修复后的代码
App.js
import "./App.css"; import Card from "./components/Card"; import NavBar from "./components/NavBar"; import Header from "./components/Header"; import { HiChevronDown } from "react-icons/hi"; import { useState, useMemo } from "react"; import HouseDetails from "./components/RealState/HouseDetails"; function App() { // 保存原始数据,避免筛选后丢失 const [originalItems] = useState(HouseDetails); // 存储搜索关键词 const [searchKeyword, setSearchKeyword] = useState(''); // 用useMemo缓存筛选结果,只有当原始数据或搜索关键词变化时才重新计算 const filteredData = useMemo(() => { if (!searchKeyword) return originalItems; return originalItems.filter(item => item.name.toLowerCase().includes(searchKeyword.toLowerCase()) ); }, [originalItems, searchKeyword]); return ( <div className="w-full justify-center"> <Header /> <main className="w-[76%] m-auto"> <div className="my-14 py-2"> <div className="sm:flex justify-between items-center"> <ul> <li className="hidden sm:block lg:text-4xl font-semibold"> Search properties to rent </li> </ul> <ul className="flex items-center justify-between border-2 border-gray-300 px-3 py-2 rounded-md text-gray-600"> <li className="font-semibold"> <input type="search" className="outline-none" placeholder="Search with Search Bar" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} /> </li> <li> <HiChevronDown /> </li> </ul> </div> <NavBar /> </div> {/* main-content */} <div className="grid xl:grid-cols-3 lg:grid-row-3 md:grid-cols-2 lg:gap-8 md:gap-6 sm:gap-4"> <Card items={filteredData} /> </div> </main> </div> ); } export default App;
Card.js(优化重渲染)
import React from "react"; import { FiHeart } from "react-icons/fi"; import { MdBed } from "react-icons/md"; import { BiBath, BiArea } from "react-icons/bi"; // 用React.memo包裹,避免props未变化时重渲染 const Card = React.memo(({ items }) => { return items?.map((elem) => { const { id, image, name, location, price, beds, bathrooms, area } = elem; return ( <article key={id} className="flex-col justify-center rounded-lg shadow-md max-w-xl"> <div className="rounded-lg"> <img src={require('./house1.jpg')} alt={name} className="rounded-lg h-72 w-full" /> </div> <div className="flex-col justify-center px-4 py-2 space-y-3"> <div className="flex-col justify-center space-y-1"> <ul className="flex justify-between items-center"> <li className="text-purple-600 font-bold text-2xl"> ${price} <span className="text-sm text-gray-400 font-semibold"> /month </span> </li> <li className="cursor-pointer p-2 rounded-full border-2 border-gray-300 text-xl text-purple-600"> <FiHeart /> </li> </ul> <ul> <li className="font-bold text-gray-600 text-2xl">{name}</li> </ul> <ul> <li className="text-gray-400 text-sm">{location}</li> </ul> </div> <div className="flex justify-between items-center py-3"> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <MdBed /> </li> <li className="text-xs text-gray-400 font-semibold"> {beds} Beds </li> </ul> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <BiBath /> </li> <li className="text-xs text-gray-400 font-semibold"> {bathrooms} Bathrooms </li> </ul> <ul className="flex justify-between items-center"> <li className="text-xl pr-2 text-purple-600"> <BiArea /> </li> <li className="text-xs text-gray-400 font-semibold"> {area} m2 </li> </ul> </div> </div> </article> ); }); }); export default Card;
内容的提问来源于stack exchange,提问作者John Soldi
相关产品推荐
相关产品推荐

