让 React JS 在 运行 之前等待异步函数完成

Make React JS await for a async func to complete before running

我试图让 react 在 axios get 请求完成之前不加载。我对周围的反应很粗鲁,所以提前道歉。

我正在获取一组对象 const { dogBreedsTest } = useApplicationData()

我需要它成为我的一个状态的默认值 const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);

但是,我收到一条错误消息,指出在我的应用程序启动的第一次迭代中我的值变为 null。在我的应用程序尝试使用它之前,如何确保我的值已完成我的请求?

这是我获取 useApplicationData() 数据的方式

const [dogBreedsTest, setDogBreeds] = useState(null);

  const getDogBreeds = async () => {
    try{
      const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
      if(data) {
        const newDogList = generateDogsArray(data['message'])
        const generatedDogs = selectedDogs(newDogList)
        setDogBreeds(generatedDogs)
        
      }
    } catch(err) {
      console.log(err);
    }
  }
  
  useEffect(() => {
    getDogBreeds()
  }, []);

  return {
    dogBreedsTest,
    setDogBreeds
  }

我正在导入我的应用程序并使用:

import useApplicationData from "./hooks/useApplicationData";

const { dogBreedsTest } = useApplicationData()
  const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
  const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])

function handleOnDragEnd(result) {
    if (!result.destination) return;

    const items = Array.from(dogBreeds);
    const [reorderedItem] = items.splice(result.source.index, 1);
    items.splice(result.destination.index, 0, reorderedItem);
    for (const [index, item] of items.entries()) {
      item['rank'] = index + 1
    }
    updateDogBreeds(dogBreedsTest[0]);
    updateBreedList1(dogBreedsTest[0])
  }
return (
<div className="flex-container">
      <div className="App-header">
          <h1>Dog Breeds 1</h1>
          <DragDropContext onDragEnd={handleOnDragEnd}>
            <Droppable droppableId="characters">
              {(provided) => (
                <ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
                  {breedList1?.map(({id, name, rank}, index) => {
                    return (
                      <Draggable key={id} draggableId={id} index={index}>
                        {(provided) => (
                          <li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
                            <p>
                              #{rank}:  { name }
                            </p>
                          </li>
                        )}
                      </Draggable>
                    );
                  })}
                  {provided.placeholder}
                </ul>
              )}
            </Droppable>
            </DragDropContext>
        </div>
)

错误:TypeError: Cannot read property 'map' of null (我在后面的程序中映射数据)

问题是,react 先渲染然后 运行useEffect(),所以如果你不想在 axios 之前渲染任何东西,你需要告诉 react,第一个渲染是空。

你的地图函数在哪里,看代码?给你看?

我想你的数据首先是空的。所以你可以使用类似的东西。

if(!data) return null

第二个选项:

    In your map try this:

   {breedList1 === null 
  ? null 
  : breedList1.map(({id, name, rank}, index) => (
     <Draggable 
      key={id} draggableId={id} index={index}>
                        {(provided) => (
                          <li ref={provided.innerRef}   {...provided.draggableProps} {...provided.dragHandleProps}>
                            <p>
                              #{rank}:  { name }
                            </p>
                          </li>
                        )}
                      </Draggable> ))}

你有 null,因为你的 axios 是异步的,并且反应在任何效果之前尝试渲染。所以如果你说 react 列表是空的,react 会在第二次渲染和加载 api 中的数据。

选项 1 使用可选的链接运算符

dogBreedsTest?.map()

如果 dogBreedsTest 是数组

,则选项 2 检查 return
retrun (<>
    {Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)

选项 3 return早

if (!Array.isArray(dogBreedsTest)) return null

retrun (<>
    {dogBreedsTest.map()}
</>)

选项 4 设置初始状态

const [dogBreedsTest, setDogBreeds] = useState([]);

您还可以添加加载状态并添加加载微调器或类似的东西:

const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)

  const getDogBreeds = async () => {
    setLoading(true)

    try{
      const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
      if(data) {
        const newDogList = generateDogsArray(data['message'])
        const generatedDogs = selectedDogs(newDogList)
        setDogBreeds(generatedDogs)
        
      }
    } catch(err) {
      console.log(err);
    }

    setLoading(false)
  }
  
  useEffect(() => {
    getDogBreeds()
  }, []);

  return {
    dogBreedsTest,
    loading,
    setDogBreeds
  }

编辑

尝试使用 useEffect 挂钩在设置 dogBreedsTest 时更新状态。

const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])

useEffect(() => {
    updateDogBreeds(dogBreedsTest?.[0] ?? [])
    updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
const getDogBreeds = async () => {
  try {
    const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
      if(data) {
        const newDogList = generateDogsArray(data['message'])
        const generatedDogs = selectedDogs(newDogList)
        setDogBreeds(generatedDogs)
      }
  } catch(err) {
      console.log(err);
  }
}
  
useEffect(() => {
  getDogBreeds() // -> you are not awaiting this
}, []);

改为这样做

useEffect(() => {
  axios.get('https://dog.ceo/api/breeds/list/all')
    .then(res => {
      const newDogList = generateDogsArray(res.data['message']);
      const generatedDogs = selectedDogs(newDogList);
      setDogBreeds(generatedDogs);
    })
    .catch(err => console.log(err));
}, []);

我知道这看起来很糟糕,但我认为您不应该在 useEffect

中使用 async/await

在您的应用程序中使用它

useEffect 将在 dogBreedsTest 更改时更新。为了使其工作,从 null 值开始,并在 async 操作完成后将它们更新为正确的初始值。

const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);

useEffect(() => {
  updateDogBreeds(dogBreedsTest[0]);
  updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);