首页 > 解决方案 > 在 useEffect 内模拟 React 设置 2 个状态

问题描述

我有一个组件,用于显示这样的数据条目列表(简化):

// resource is the rest endpoint, 
// items is the parents components 
// state that holds the data entries and setItems is the corresponding setter
export default function LoadedList({resource,items, setItems,CustomFormat}){
    const [loadingOrError,setLoadingOrError] =useState(false)

    useEffect(()=>{
       axios.get(baseURL+resource)
            .then((e)=>{
                setItems(e.data)
                setLoadingOrError(false)
            })
            .catch((e)=>{
                setItems([{text:"Error"}])
                setLoadingOrError(true)
            })
            setItems([{text:"Loading...-"}])
            setLoadingOrError(true)
    },[])
   
    return(
          <div className="list">
              {
                    items.map((item)=>
                        loadingOrError?
                             <DefaultFormat item={item} />
                        :
                             <CustomFormat item={item}/>
                    )
              }
          </div>
    )
}

基本思想是,当组件加载项目或失败时,应使用默认格式显示相应的消息。成功加载项目后,应使用父项的格式来格式化条目。问题是,我发现 setItems 和 setLoading 没有同时更改。它似乎工作的方式是它首先 setItems 然后重新呈现所有条目,然后才将 loadingOrError 更改为 true。那么有没有办法同时设置这两个?或者只是不重新渲染其间的一切?

标签: reactjs

解决方案


与其尝试同时更新两者,不如尝试分别跟踪加载和错误状态,然后执行以下操作:

// resource is the rest endpoint, 
// items is the parents components 
// state that holds the data entries and setItems is the corresponding setter
export default function LoadedList({resource, items, setItems, CustomFormat}){
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState("");

    useEffect(()=>{
       setLoading(true);
       axios.get(baseURL+resource)
            .then((e)=>
                setItems(e.data)
            )
            .catch((e)=>
                setError("Error")
            )
            .finally(() => setLoading(false));
    },[])

    if(loading) {
      return "Loading ...";
    }

    if(error) {
      return error;
    }   

    return(
          <div className="list">
              {items.map((item, index) => <CustomFormat key={index} item={item}/>)}
          </div>
    )
}

这应该会显示Loading...,直到所有项目都加载完毕。

如果您坚持要保持一切原样,并且只是实现您最初要求的同时更新两者,您可能需要定义一个函数来执行上一级的 API 调用,以及加载状态,错误状态和数据状态处理,将所有这些状态放在同一个状态钩子下,然后将 API 函数传递给孩子的 useEffect 使用。

const [dataState, setDataState] = useState({
  data: null,
  loading: false,
  error: ""
});

...

setDataState({data: data, loading: false});

除此之外,我推荐两件事:

  • 您应该在请求完成时和设置状态之前检查组件是否仍然挂载。否则你会得到一个错误。这很容易通过一个额外的变量来跟踪挂载状态。
  • 为处理请求创建一个自定义钩子可能是有益的,因为这可能是您经常要做的事情,并且在每种情况下看起来都非常相似。我发现这篇文章中的分步指南非常清楚。

取自那篇文章:

useFetch 自定义钩子

import { useState, useEffect } from 'react';

const useFetch = (url = '', options = null) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let isMounted = true;

    setLoading(true);

    fetch(url, options)
      .then(res => res.json())
      .then(data => {
        if (isMounted) {
          setData(data);
          setError(null);
        }
      })
      .catch(error => {
        if (isMounted) {
          setError(error);
          setData(null);
        }
      })
      .finally(() => isMounted && setLoading(false));

    return () => (isMounted = false);
  }, [url, options]);

  return { loading, error, data };
};

export default useFetch;

推荐阅读