React-Redux中API数据过滤变量无法初始化填充的问题排查
问题
我用React+Redux从API获取数据,创建了filterClassifieds变量用来处理数据并展示结果,但页面加载时这个变量始终为空,没法从原始数据源classifieds填充数据,导致表格显示0条结果。请问要在React组件还是Redux的actions/reducers里修改实现?
补充说明:初始化dispatch前classifieds和filterClassifieds都为空,但数毫秒后classifieds已填充数据,filterClassifieds仍然没有数据。
相关代码
classifieds.js
import React, { useState,useEffect,useMemo } from "react"; import './classifieds.scss' import { OverlayBox } from "../../components/Main-Pages/overlay-box/overlay-box"; import { SideFilter } from "../../components/Main-Pages/side-filter/side-filtrer"; import Table from 'react-bootstrap/Table'; import { NavLink } from 'react-router-dom'; import Card from 'react-bootstrap/Card'; import { useDispatch, useSelector } from 'react-redux'; import SpinnerButton from "../../components/Spinner/Spinner"; import { getClassifieds } from "../../redux/actions/classifiedsAction"; export const Classifieds = ()=>{ const dispatch = useDispatch(); const buttons = ['Show All','Rental','Services','Wanted','Cars','For Sale', 'More']; const moreButtons = ['Baby sitting','Beauty parlor','Fashion','Household Help' ,'Real Estate','Others']; const [active,setActive] = useState('Show All'); const [activeMoreButtons,setActiveMoreButtons] = useState(''); const [modal,setModal] = useState(false); const classifieds = useSelector((state) => state.classifieds.Classifieds); const classifiedsLoading = useSelector((state) => state.classifieds.loading); // const [filterClassifieds,setFilterClassifieds] = useState(classifieds); // useEffect(() => { // setFilterClassifieds(classifieds); // }, [classifieds]); const filterClassifieds = classifieds.filter((item) => { return active !== "Show All" ? item.category === active : true; }); const handleButton = (name)=>{ if(modal)setModal(false); setActiveMoreButtons(''); setActive(name); // if(name==='Show All'){ // setFilterClassifieds(classifieds); // }else{ // let newClassifieds = classifieds.filter((itm)=> // { // return itm.category === name; // } // ) // setFilterClassifieds(newClassifieds); // } } const openModal =()=>{ setModal(!modal); } const handleMoreButton=(name)=>{ setActive('More'); setModal(!modal); setActiveMoreButtons(name) } useEffect(() => { const generateClassifieds = async () => { await dispatch(getClassifieds()); }; generateClassifieds(); }, [dispatch]); const transformDate = (datePosted)=> { let newDate = new Date(datePosted); return newDate.toLocaleDateString("en-US"); } // console.log(filterClassifieds); return( <div> <OverlayBox name={"Classifieds"}/> <div className="section properties"> <div className="container"> <ul className="properties-filter"> { buttons.map((name,index)=>{ return name!=='More' ?( <li key={ index }> <button className={active === name ? 'is_active' : ''} onClick={()=>handleButton(name)} >{name}</button> </li> ):( <li key={ index }> <button className={active === name ? 'is_active' : ''} onClick={()=>openModal(name)} > {name} <i className="fa fa-arrow-down-short-wide"></i> </button> { modal? <Card body className='card-design'> <ul> {moreButtons.map((val,index)=>( <li className={activeMoreButtons === val ? 'card-active card-li' : 'card-li'} key={index} onClick={()=>handleMoreButton(val)}> {val} </li> ))} </ul> </Card>:<></> } </li> ) }) } </ul> <div className="row properties-box"> <div className="col-9 properties-header"> <p className="properties-text"> Free Classifieds </p> </div> <div className="col-3 text-end properties-text"> <NavLink to='/post-ad' className='col-lg-2'>Post Ad</NavLink> </div> <Table striped hover> <thead> <tr> <th>Category</th> <th>Description</th> <th>Date Posted</th> </tr> </thead> {classifiedsLoading?<></>: <tbody> {filterClassifieds?.map((val,ind)=>( <tr key={ind}> <td>{val?.category}</td> <td>{val?.heading.split('', 86).reduce((o, c) => o.length === 85 ? `${o}${c}...` : `${o}${c}` , '')}</td> <td>{transformDate(val?.date)}</td> </tr> ))} </tbody> } </Table> </div> </div> </div> </div> ) }
action.js
import axios from "axios"; export const GET_CLASSIFIEDS = "GET_CLASSIFIEDS"; export const CLASSIFIEDS_LOADING = "CLASSIFIEDS_LOADING"; // Get all posts export const getClassifieds = () => (dispatch) => { dispatch(setClassifiedsLoading()); axios .get("/posts") .then((res) => dispatch({ type: GET_CLASSIFIEDS, payload: res.data, }) ) .catch((err) => dispatch({ type: CLASSIFIEDS_LOADING, payload: {}, }) ); }; // Classifieds loading export const setClassifiedsLoading = () => { return { type: CLASSIFIEDS_LOADING, }; };
reducer.js
import { GET_CLASSIFIEDS, CLASSIFIEDS_LOADING } from "../actions/classifiedsAction"; const initialState = { Classifieds: null, filterClassifieds: null, loading: true, }; export const ClassifiedsReducer = (state = initialState, action) => { switch (action.type) { case GET_CLASSIFIEDS: return { ...state, Classifieds: action.payload, loading: false, }; case CLASSIFIEDS_LOADING: return { ...state, loading: true, }; default: return state; } };
原因分析
问题出在filterClassifieds的定义逻辑:
- 初始状态下Redux中的
classifieds是null,直接调用classifieds.filter()会触发隐性错误,导致filterClassifieds无法正常生成。 - 后续
classifieds获取到数据后,组件重新渲染时虽然会重新执行过滤代码,但之前的错误逻辑已经影响了变量的正常赋值。 - 过滤属于组件UI状态相关的派生逻辑,完全不需要放到Redux的actions/reducers中处理。
解决方案
在React组件中修改filterClassifieds的定义,用useMemo优化性能并处理classifieds为空的情况:
// 替换原来的filterClassifieds定义 const filterClassifieds = useMemo(() => { if (!classifieds) return []; return classifieds.filter((item) => { return active !== "Show All" ? item.category === active : true; }); }, [classifieds, active]);
说明
- 先判断
classifieds是否存在,为空时返回空数组,避免调用filter方法报错。 - 用
useMemo缓存过滤结果,只有当classifieds或active状态变化时才重新执行过滤,减少不必要的渲染开销。 - 当API返回数据后,
classifieds更新,useMemo会自动重新计算过滤结果,表格就能正常渲染数据。
内容的提问来源于stack exchange,提问作者kheman garg
相关产品推荐
相关产品推荐

