React实现搜索框实时过滤Redux存储的帖子列表出现死循环问题
问题核心原因
- 依赖项缺失:
useEffect未将keyword、posts加入依赖数组,输入搜索关键词、拉取到新帖子数据时都不会重新执行过滤逻辑 - 渲染数据源错误:JSX中遍历的是原始未过滤的
posts数组,而非你处理后的过滤结果 - 逻辑顺序颠倒:
useEffect中先执行过滤,再拉取帖子数据,新拉取到的帖子无法在本次执行流中完成过滤
修复方案
你可以选择两种实现方式,第一种是直接派生过滤结果(更简洁,无额外state开销),第二种是保留state的写法:
方案1:删除多余state,直接派生过滤列表(推荐)
不需要单独用useState存过滤后的帖子,每次渲染时基于posts和keyword实时计算即可,数据量大的话可以加useMemo优化性能:
import { useEffect, useState, useMemo } from 'react'; import { Col, Container, Form, Row } from 'react-bootstrap'; import { ToastContainer } from 'react-toastify'; import { useDispatch, useSelector } from 'react-redux'; import { listPosts } from '../actions/postActions'; import Banner from '../components/Banner'; import Post from '../components/Post'; import Paginate from '../components/Paginate'; const HomePage = ({ match }) => { const [keyword, setKeyword] = useState(''); const pageNumber = match.params.pageNumber || 1; const dispatch = useDispatch(); const postLists = useSelector((state) => state.postLists); const { posts, pages, page } = postLists; const postCreate = useSelector((state) => state.postCreate); const { success: successCreate } = postCreate; const postUpdate = useSelector((state) => state.postUpdate); const { success: successUpdate } = postUpdate; const postDelete = useSelector((state) => state.postDelete); const { success: deleteSuccess } = postDelete; // 直接派生过滤后的帖子列表,依赖变化时自动重新计算 const filteredPosts = useMemo(() => { if (!posts) return []; return posts.filter((post) => post.title.toLowerCase().includes(keyword.toLowerCase()) ); }, [posts, keyword]); // 这个effect只负责拉取帖子数据,不需要处理过滤逻辑 useEffect(() => { dispatch(listPosts(pageNumber)); }, [dispatch, pageNumber, deleteSuccess, successCreate, successUpdate]); return ( <div> <ToastContainer /> <Banner /> <div className="my-3"> <Container id="posts"> <Form className="searchBoxForm"> <Form.Control type="text" name="q" onChange={(e) => setKeyword(e.target.value)} placeholder="Search Posts..."></Form.Control> </Form> <h2 className="mt-3" style={{ letterSpacing: '4px' }}> POSTS </h2> <Row className="mt-3"> <Col> <Row> {/* 渲染过滤后的列表 */} {filteredPosts ?.map((post) => ( <Col key={post._id} md={3} sm={4}> <Post post={post} /> </Col> )) .sort() .reverse()} </Row> <Paginate pages={pages} page={page} /> </Col> </Row> </Container> </div> </div> ); }; export default HomePage;
方案2:保留state存过滤结果的写法
如果需要保留listPosts2这个state,拆分两个useEffect分别处理数据拉取和过滤逻辑即可避免循环:
// 只负责拉取数据 useEffect(() => { dispatch(listPosts(pageNumber)); }, [dispatch, pageNumber, deleteSuccess, successCreate, successUpdate]); // 只负责过滤更新state useEffect(() => { const filteredPostList = posts?.filter((post) => post.title.toLowerCase().includes(keyword.toLowerCase()) ) || []; setListPosts2(filteredPostList); }, [posts, keyword]);
之后把渲染部分的posts替换成listPosts2即可。
内容的提问来源于stack exchange,提问作者dev_Dragon
相关产品推荐
相关产品推荐

