React开发环境组件重载后报allResults.filter is not a function错误
编辑说明:经测试该功能在生产环境可正常运行,仅开发环境下无法正常使用。
问题现象
开发通讯录应用时,首次加载流程完全正常:用于全量联系人加载的自动补全组件Busqueda可以成功拉取联系人数据,选中任意联系人后,关联组件都能正常渲染对应信息。
但在配置完联系人、子联系人的编辑/删除按钮后,执行任意编辑/删除操作触发reload动作刷新联系人列表展示最新改动时,控制台抛出allResults.filter is not a function错误,排查发现触发错误时allResults是对象类型,不符合预期的数组类型。
Agenda组件原代码
function Agenda() { const MiContexto = useOutletContext(); useEffect(()=>{MiContexto.CrearContactoModalDispatch(false); },[]); const [selectedValue, setSelectedValue] =useState(null); const [selectedSubContact, setSelectedSubContact] =useState(null); useEffect(()=>{ MiContexto.setSelectedContact(selectedValue); MiContexto.setAgenda_nombre_empresa(selectedValue?.nombre_empresa); },[selectedValue]); const [allResults, setAllResults] =useState([]); const [coincideArray, setCoincideArray] =useState(allResults?.filter(entry => entry?.empresa_id === selectedValue?.empresa_id)); const [reloadBusqueda,setReloadBusqueda] = useState(0); useEffect(()=>{ setCoincideArray(allResults?.filter(entry => entry?.empresa_id === selectedValue?.id_empresa)); },[allResults!==null, selectedValue]); return ( <div className={'outletBasicSetting'}> <Busqueda setSelectedValue={setSelectedValue} allResults={setAllResults} reload={reloadBusqueda}/> <Grid container> <Grid item xs={12} md={6}> <EntidadPrincipal selectedValue={selectedValue}/> </Grid> <Grid container item xs={12} md={6}> {coincideArray[0]?.id_empresa && coincideArray?.map((persona)=>{ return( <Grid key={persona.id_contacto} item xs={12} md={6}> <SubEntidad persona={persona}/> </Grid>) })} </Grid> </Grid> <CrearContacto opening={MiContexto.CrearContactomodal} setReloadBusqueda={setReloadBusqueda}/> {coincideArray[0]?.id_empresa ? <EditarQuien openQuien={MiContexto.quienModal} contacto={selectedValue} coincideArray={coincideArray} setReloadBusqueda={setReloadBusqueda} setSelectedSubContact={setSelectedSubContact}/> : <EditarContactoP opening={MiContexto.quienModal} setReloadBusqueda={setReloadBusqueda} contacto={selectedValue}/>} <EditarContactoP opening={MiContexto.EditarPModal} setReloadBusqueda={setReloadBusqueda} contacto={selectedValue}/> <EditarContactoS opening={MiContexto.EditarCModal} setReloadBusqueda={setReloadBusqueda} contacto={selectedSubContact}/> <CrearSubContacto opening={MiContexto.crearSubContactoModal} setReloadBusqueda={setReloadBusqueda} contacto={selectedValue}/> <BorrarQuien openBorrarQuien={MiContexto.borrarCualContactoAgendaModal} contacto={selectedValue} coincideArray={coincideArray} setReloadBusqueda={setReloadBusqueda} setSelectedSubContact={setSelectedSubContact}/> </div> ); }
Busqueda组件原代码
import { Autocomplete, Stack, TextField, Chip, Avatar, autocompleteClasses, styled, Popper } from '@mui/material'; import React, { useEffect, useState } from 'react'; import ListboxComponent from './VariableSizeList'; import { toast } from 'react-toastify'; function Busqueda({setSelectedValue,allResults,reload}){ const [jsonResults, setJsonResults] = useState([]); var token = ""; const [selected, setSelected] = useState( null); const [storedSelected,setStoredSelected] = useState(null); const [open, setOpen] = React.useState(false); const handleChange = (e,v) =>{ setSelected(v); setStoredSelected(v); }; useEffect(()=>{ token=JSON.parse(localStorage.getItem('token')); },[]); useEffect(()=>{ setSelectedValue(selected); allResults(jsonResults); },[allResults, jsonResults, selected, setSelectedValue]); const StyledPopper = styled(Popper)({ [`& .${autocompleteClasses.listbox}`]: { boxSizing: 'border-box', '& ul': { padding: 0, margin: 0, }, }, }); useEffect(() => { console.time("Tiempo de carga de LeerAgenda"); fetch(process.env['REACT_APP_DEV_PROXY']+"https://server.com/file.php",{ method: 'POST', headers:{'Token':token["token"]}, body: "", }) .then((response) => response.json()) .then((json)=>{ setJsonResults(json); console.timeEnd("Tiempo de carga de LeerAgenda"); setSelected(null); }) .catch((err) => { toast.error(JSON.stringify(err["msg"])); console.error("Se catcheo un error: "+err["msg"]); }) },[reload]) return ( <Autocomplete open={open} onInputChange={(_, value) => { if (value.length === 0) { if (open) setOpen(false); } else { if (!open) setOpen(true); } }} onClose={() => setOpen(false)} value={selected} id="agenda" disableListWrap ListboxComponent={ListboxComponent} getOptionLabel={jsonResults => jsonResults?.nombre_contacto ? jsonResults?.nombre_empresa +": "+jsonResults?.nombre_contacto : jsonResults?.nombre_empresa } options={jsonResults} PopperComponent={StyledPopper} renderInput={(params) => <TextField {...params} label="Busca aquí el contacto"/>} renderOption={(props, jsonResults) => [props, <> <Chip label={jsonResults.nombre_empresa} color="primary" size="small" avatar={<Avatar>{jsonResults.nombre_empresa.charAt(0)}</Avatar>}/> {jsonResults?.nombre_contacto}</> ]} onChange={handleChange} isOptionEqualToValue={(option,value) => option.id_contacto === value.id_contacto} noOptionsText={"Tu texto no coincide con ningún resultado"} /> </Stack>) } export default Busqueda;
问题根因
- 核心错误在
Busqueda组件的useEffect配置:父组件传入的allResults本质是setAllResults状态更新函数,和selectedValue、jsonResults放在同一个effect依赖数组中,导致多个状态更新互相触发。首次加载时effect只执行一轮,流程正常;触发reload后,effect会在selected值重置前先执行一轮状态回传,加上开发环境默认启用React严格模式,会重复挂载组件、重复执行useEffect,放大了依赖配置错误的影响,导致父组件的allResults被意外赋值为非数组的对象类型,调用.filter直接报错,这也是生产环境正常、开发环境报错的核心原因。 - 父组件监听
allResults变化的useEffect依赖写为allResults!==null,这是个固定布尔值,完全无法正确监听allResults的实际变化。 - 接口返回值没有做类型校验,一旦后端返回非数组结构(比如报错返回对象),也会直接触发同类错误。
修复方案
1. 修正Busqueda组件的effect逻辑
拆分两个独立effect分别处理选中值回传、全量结果回传,同时增加类型兜底,避免非数组值传入状态:
// 选中联系人变化时,单独回传选中值 useEffect(()=>{ setSelectedValue(selected); },[selected, setSelectedValue]); // 联系人列表数据变化时,单独回传全量列表 useEffect(()=>{ if(Array.isArray(jsonResults)){ allResults(jsonResults); }else{ allResults([]); } },[jsonResults, allResults]);
同时在接口返回处增加类型校验,确保存入状态的永远是数组:
.then((json)=>{ setJsonResults(Array.isArray(json) ? json : []); console.timeEnd("Tiempo de carga de LeerAgenda"); setSelected(null); })
2. 修正父组件Agenda的useEffect依赖
替换错误的布尔值依赖,同时增加数组类型判断做兜底:
useEffect(()=>{ if(Array.isArray(allResults)){ setCoincideArray(allResults.filter(entry => entry?.empresa_id === selectedValue?.id_empresa)); }else{ setCoincideArray([]); } },[allResults, selectedValue]);
修改完成后,reload触发时全量联系人列表会始终保持数组类型,不会再出现filter调用报错的问题。
内容的提问来源于stack exchange,提问作者Germán
相关产品推荐
相关产品推荐

