axios React的打字稿:在useEffect中使用自定义挂钩

wpx232ag  于 2022-11-05  发布在  iOS
关注(0)|答案(2)|浏览(178)

我尝试在useEffect调用中使用一个钩子,以便只运行一次(并加载一些数据)。我一直收到无法执行此操作的错误(尽管我在另一个应用程序中做了完全相同的事情,不知道为什么一个工作,另一个不工作),我知道我可能打破了挂钩规则...所以,我的目标是将所有的CRUD操作逻辑卸载到一个简单的钩子中。下面是MenuItem,它是尝试使用钩子获取数据的组件。

const MenuItem = () => {
  const [ID, setID] = useState<number | null>(null);
  const [menu, setMenu] = useState<Item[]>([]);
  const { getMenu, retrievedData } = useMenu();

  //gets menu items using menu-hook
  useEffect(() => {
    getMenu();
  }, []);

  //if menu is retrieved, setMenu to retrieved data
  useEffect(() => {
    if (retrievedData.length) setMenu(retrievedData);
  }, []);

  //onClick of menu item, displays menu item description
  const itemHandler = (item: Item) => {
    if (ID === null || ID !== item._id) {
      setID(item._id);
    } else {
      setID(null);
    }
  };
return ...
};

下面是getMenu,它是处理逻辑和数据检索的自定义钩子。

const useMenu = () => {
  const backendURL: string = 'https://localhost:3001/api/menu';
  const [retrievedData, setRetrievedData] = useState<Item[]>([]);

  const getMenu = async () => {
    await axios
      .get(backendURL)
      .then((fetchedData) => {
        setRetrievedData(fetchedData.data.menu);
      })
      .catch((error: Error) => {
        console.log(error);
        setRetrievedData([]);
      });
  };

  return { getMenu, retrievedData };
};

export default useMenu;

最后是错误。

Invalid hook call. Hooks can only be called inside of the body of a function component.

我想补充的是,我也在使用Typescript,它现在没有抱怨。

gxwragnw

gxwragnw1#

你可以做一些事情来改进这段代码,这对将来可能会有帮助。你是对的,你打破了钩子的规则,但是没有必要!如果你把fetch移出钩子(没有必要在每次渲染时重新定义它),那么它不存在于deps数组中是有效的,因为它是一个常量。
我还会让您的useMenu钩子为您处理加载/返回加载值的所有细节。

const fetchMenu = async () => {
  const backendURL: string = 'https://localhost:3001/api/menu';

  try {
    const { data } = await axios.get(backendURL);
    return data.menu;
  } catch (error: AxiosError) {
    console.log(error);
    return [];
  };
}

export const useMenu = () => {
  const [items, setItems] = useState<Item[]>([]);

  useEffect(() => {
    fetchMenu.then(result => setItems(result);
  }, []);

  return items;
};

现在您可以使用钩子:

const MenuItem = () => {
  const [ID, setID] = useState<number | null>(null);

  // Now this will automatically be an empty array while loading, and
  // the actual menu items once loaded.
  const menu = useMenu();

  // --- 8< ---

  return ...
};

还有几件事-

  • 尽量避免默认导出,因为default exports are terrible
  • 这里有很多软件包可以让你的工作变得更轻松!react-query是一个很好的例子,因为它将管理外部数据的所有生命周期/状态管理
  • 或者,看看react-use,它是一个自定义钩子的集合,可以帮助处理很多类似的常见情况。你可以使用useAsync钩子来简化上面的useMenu钩子:
const backendURL: string = 'https://localhost:3001/api/menu';

const useMenu = () => useAsync(async () => {
  const { data } = await axios.get(backendURL);
  return data.menu;
});

现在来吃这个钩子:

const MenuItem = () => {
  const { value: menu, loading, error } = useMenu();

  if (loading) {
    return <LoadingIndicator />;
  }

  if (error) {
    return <>The menu could not be loaded</>;
  }

  return ...
};

useAsync不仅能够在钩子提取时显示加载指示符,而且如果在async函数完成加载之前卸载了组件(上面的代码没有处理),useAsync也不会给您发出内存泄漏警告。

jv4diomz

jv4diomz2#

在这个项目上工作了一段时间后,我也发现了另一个干净的解决方案,我相信它不会破坏钩子的规则。这需要我设置一个自定义的http钩子,它使用一个sendRequest函数来处理应用程序范围内的请求。让我澄清一下,这不是一个简单的解决方案,我确实增加了复杂性,但我相信它会有所帮助,因为我将在应用程序中发出多种不同类型的请求。

const sendRequest = useCallback(
    async (url: string, method = 'GET', body = null, headers = {}) => {
      setIsLoading(true);
      const httpAbortCtrl = new AbortController();
      activeHttpRequests.current.push(httpAbortCtrl);

      try {
        const response = await fetch(url, {
          method,
          body,
          headers,
          signal: httpAbortCtrl.signal,
        });
        const responseData = await response.json();

        activeHttpRequests.current = activeHttpRequests.current.filter(
          (reqCtrl) => reqCtrl !== httpAbortCtrl
        );

        if (!response.ok) throw new Error(responseData.message);
        setIsLoading(false);
        return responseData;
      } catch (error: any) {
        setError(error);
        setIsLoading(false);
        throw error;
      }
    },
    []
  );

这是新的useMenu钩子,注意我不需要返回getMenu,因为每次在我的应用中使用sendRequest时,都会自动调用getMenu。

export const useMenu = () => {
  const { sendRequest } = useHttpClient();
  const [menu, setMenu] = useState<MenuItem[]>([]);
  const [message, setMessage] = useState<string>('');

  useEffect(() => {
    const getMenu = async () => {
      try {
        const responseData = await sendRequest(`${config.api}/menu`);
        setMenu(responseData.menu);
        setMessage(responseData.message);
      } catch (error) {}
    };
    getMenu();
  }, [sendRequest]);

  return { menu, message };
};

祝你好运

相关问题