You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>}/>
                 &nbsp;{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;
问题根因
  1. 核心错误在Busqueda组件的useEffect配置:父组件传入的allResults本质是setAllResults状态更新函数,和selectedValue、jsonResults放在同一个effect依赖数组中,导致多个状态更新互相触发。首次加载时effect只执行一轮,流程正常;触发reload后,effect会在selected值重置前先执行一轮状态回传,加上开发环境默认启用React严格模式,会重复挂载组件、重复执行useEffect,放大了依赖配置错误的影响,导致父组件的allResults被意外赋值为非数组的对象类型,调用.filter直接报错,这也是生产环境正常、开发环境报错的核心原因。
  2. 父组件监听allResults变化的useEffect依赖写为allResults!==null,这是个固定布尔值,完全无法正确监听allResults的实际变化。
  3. 接口返回值没有做类型校验,一旦后端返回非数组结构(比如报错返回对象),也会直接触发同类错误。
修复方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 08:24:10