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

为何设置key后React组件状态未重置?filter_obj变更异常

问题:filter_obj变化时组件状态无法彻底重置

我希望在filter_obj发生变化时重置组件之前的状态,虽然已经在useEffect里重置了page和itemData,但它们会立刻恢复成原来的值。

父组件里给Archive组件设置了key:

<Archive key = {filter_obj.length} state={state_filter}/>

理论上filter_obj长度变化时,旧组件会被销毁、新组件重新挂载,所有状态应该被清空,但itemData还是会恢复成之前的值。

以下是使用react-infinite-scroll-component的组件代码:

export default function Archive(props) {
    const [width, height] = useWindowSize()
    const [manifest, setManifest]= useState({});
    const [open, setOpen] = useState(false);
    const [apiUrl,setapiurl] = useState([])
    const [itemData, setData] = useState([])
    const [hasMore, setHasMore] = useState(true)
    const {filter_obj, set_filter_obj} = props.state;

    const [page, setPage] = React.useState(0);
    const [rowsPerPage, setRowsPerPage] = React.useState(16);
    const [total, setTotal] = useState(0)

    const handleOpen = (manifest) => {
      setOpen(true);
      setManifest(manifest)}
    const handleClose = () => setOpen(false);
    const modalStyle = {
        position: "absolute",
        top: "50%",
        left: "50%",
        transform: "translate(-50%, -50%)",
        p: 4,
        maxHeight: 500,
      };


      const fetchData = async () => {
      var data = new FormData();

        data.append("hierarchical", "False");
        
        data.append("selected_fields", 'url');
        data.append("results_per_page", rowsPerPage);
        data.append("results_page", page + 1);
         for (const property in filter_obj) {
          filter_obj[property].forEach((v) => {
            data.append(property, v);
          })}
       
        const res = await fetch('url',{
          method: 'POST',
          body: data,
          headers: {'Authorization':AUTH_TOKEN}
        })
        const result = await res.json();
        setapiurl(result)
        const fetchImage = async ()=>{
          const promises = result.map(uri => {
            return fetch(Object.values(uri), {
              method: "GET",
            }).then(res => {
              return res.json()
            }).then(res => {
              var dict = {"title": res.label.none[0], "image": curImage(res.thumbnail[0].id),"uri":uri}
              return dict;
            })
          })
          const response = await Promise.all(promises)
          setData([...itemData, ...response])
          
          if(response.length<=0){
            
            setHasMore(false)
          }
        
        }
        fetchImage().catch(console.error);
      }

      useEffect(()=>{
        setPage(0);
        setData([])
        setapiurl( [])   
        fetchData()
          
        
      }, [filter_obj])



    return (
      <div id="infinite-container" style={{ overflow: 'auto'}}>
      <InfiniteScroll
      dataLength={itemData.length} //This is important field to render the next data
      next={fetchData}
      getScrollParent={()=>document.getElementById('infinite-container')} 
      useWindow={false}

      hasMore={hasMore}
      loader={<h4>Loading...</h4>}
      endMessage={
        <p style={{ textAlign: 'center' }}>
          end message
        </p>
      } >


     <ImageList sx={{ width: width, height: "99%" }} cols={
              Math.sqrt(rowsPerPage)
              } gap={30} >
     
              {itemData.map((item) => (
                <ImageListItem key={item.image}>
                  <img
                    src={`${item.image}?w=248&fit=crop&auto=format`}
                    srcSet={`${item.image}?w=248&fit=crop&auto=format&dpr=2 2x`}
                    alt={item.title}
                    loading="lazy"
                  />
                  <ImageListItemBar
                    title={item.title}
                    sx ={{
                      bgcolor: alpha('#549165',0.8)
                    }}
                    actionIcon={
                      <IconButton
                        sx={{ color: 'rgba(255, 255, 255, 0.54)' }}
                        aria-label={`info about ${item.title}`}
                        onClick={() => handleOpen(item.uri.url)}
                      >
                        <InfoIcon />
                      </IconButton>
                    }
                  />
                </ImageListItem>
              ))}
              
            </ImageList>
    
        </InfiniteScroll>
        </div>
    );
}

解决方案

问题核心

  1. key取值逻辑缺陷:用filter_obj.length作为key时,如果两次筛选操作后长度相同(比如先选2个条件,取消1个后再选1个回到长度2),key值重复,React会判定为同一个组件,不会触发重新挂载,状态自然无法重置。
  2. 闭包陷阱导致状态复用:fetchData函数依赖了page和itemData的旧值,useEffect里调用fetchData时,状态更新是异步的,函数执行时拿到的还是未重置前的旧状态,导致新请求的数据和旧数据拼接在一起。

修复步骤

1. 更换组件key的取值

改用filter_obj的内容生成唯一key,确保只要筛选条件变化,组件就会重新挂载:

<Archive key={JSON.stringify(filter_obj)} state={state_filter}/>

2. 修复fetchData的闭包问题

采用函数式更新获取最新状态,同时给fetchData传入明确的页码参数,避免依赖旧状态:

const fetchData = async (currentPage) => {
  var data = new FormData();

  data.append("hierarchical", "False");
  data.append("selected_fields", 'url');
  data.append("results_per_page", rowsPerPage);
  // 使用传入的最新页码,避免闭包拿旧值
  data.append("results_page", currentPage + 1);
  
  for (const property in filter_obj) {
    filter_obj[property].forEach((v) => {
      data.append(property, v);
    });
  }

  const res = await fetch('url',{
    method: 'POST',
    body: data,
    headers: {'Authorization':AUTH_TOKEN}
  })
  const result = await res.json();
  setapiurl(result);
  
  const fetchImage = async ()=>{
    const promises = result.map(uri => {
      return fetch(Object.values(uri), {
        method: "GET",
      }).then(res => res.json())
      .then(res => {
        return {"title": res.label.none[0], "image": curImage(res.thumbnail[0].id),"uri":uri};
      })
    })
    const response = await Promise.all(promises);
    // 函数式更新,确保拿到最新的itemData
    setData(prev => [...prev, ...response]);
    setHasMore(response.length > 0);
  }
  fetchImage().catch(console.error);
}

3. 完整重置所有相关状态

在useEffect里重置状态时,别忘了重置hasMore,避免新筛选条件下无法触发请求:

useEffect(()=>{
  setPage(0);
  setData([]);
  setapiurl([]);
  setHasMore(true);
  // 传入明确的初始页码0
  fetchData(0);
}, [filter_obj])

内容的提问来源于stack exchange,提问作者Alison Qiu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 08:45:36