реагируйте на пользовательский крючок, вызывающий бесконечный цикл

#javascript #reactjs #react-hooks

Вопрос:

Я относительно новичок в крючках реагирования и пытаюсь создать этот пользовательский крючок для обработки операций CRUD для моего API.

Это файл с крючком:

 import React, { useState, useEffect } from "react";

const useApi = (url, headers = { method: "GET" }, payload = null) => {
  
  const [isLoading, setIsLoading] = useState(true);
  const [apiData, setApiData] = useState(null);
  const [serverError, setServerError] = useState(null);
  const [api, setApi] = useState({});

  const list = async () => {
    try {
      const resp = await fetch(url);
      const data = await resp?.json();
      setApiData(data);
      setIsLoading(false);
    } catch (error) {
      setServerError(error);
    } finally {
      setIsLoading(false);
    }
  };

  const create = async () => {
    try {
      const resp = await fetch(url, (headers = { method: "POST" }), payload);
      const data = await resp?.json();
      setApiData(data);
      setIsLoading(false);
    } catch (error) {
      setServerError(error);
    } finally {
      setIsLoading(false);
    }
  };

  setApi({
    ...api,
    list: list,
    create: create
  });

  return { isLoading, apiData, serverError, api };
};

export default useApi;

 

Однако, когда я вызываю api.list() в своем основном компоненте внутри крючка useEffect (), я получаю бесконечный цикл.

Пример вызова компонента:

 import { useEffect } from "react";
import useApi from "./useApi";

export default function App() {
  
  const {
    isLoading: loading,
    apiData: students,
    serverError: error,
    api
  } = useApi("https://59f0f160ce72350012bec011.mockapi.io/students");

  console.log(loading, students, error, api);

  useEffect(() => {
    api.list();
  }, [api]);

  return (
    <div className="App">
      <h1>list</h1>
      {loading ? "loading" : students.map((x) => x.name)}
    </div>
  );
}
 

Вот песочница для этого:
https://codesandbox.io/s/cocky-chebyshev-d9q89?file=/src/App.js:0-492

Кто-нибудь может помочь мне разобраться в этой проблеме? Заранее благодарю вас!

Ответ №1:

Это то, что вызывает бесконечный цикл:

 setApi({
  ...api,
  list: list,
  create: create
});
 

Вы не должны вызывать setState() во время рендеринга.

В вашем случае вам не нужно useState для объекта api, вы можете просто возвращать его при каждом рендеринге:

 return {
  isLoading,
  apiData,
  serverError,
  api: { list, create }
};
 

Вот ссылка на исправленную песочницу

Кроме того, еще одно предупреждение: этот код будет повторно вызываться api.list() .

 useEffect(() => {
  api.list();
}, [api]);
 

Поскольку api изменения происходят при каждом рендеринге, он будет повторно вызываться api.list() .

Это объект, который меняется при каждом рендеринге:

 return { isLoading, apiData, serverError, api };
 

Вы можете убедиться, что звоните только api.list() один раз, используя ссылку.

 import { useRef } from 'react'

// In the component
const gotRef = useRef(false)
useEffect(() => {
  if (!gotRef.current) {
    api.list();
    gotRef.current = true
  }
}, [api]);
 

Комментарии:

1. Спасибо @programmerRaf. Я добавил ссылку, но она все равно дает мне бесконечные циклы. Вы можете ознакомиться с проблемой здесь: codesandbox.io/s/cocky-chebyshev-d9q89?file=/src/App.js

2. @rsilva Я нашел настоящую причину бесконечного цикла. Я отредактировал ответ.