在事件处理程序中调用自定义钩子

时间:2020-12-28 03:21:22

标签: reactjs react-hooks

我有一个名为 useFetchMyApi 的自定义挂钩,用于包装对 API 端点的提取调用。函数钩子接受一个参数,它包含在帖子正文中。数据数组输出依赖于钩子参数。

在 UI 上,App 组件调用 useFetchMyApi 一次。按钮点击处理程序将调用后续 useFetchMyApi 以更新 UI 上的列表。

我的问题:

  1. 我无法在事件处理程序中调用 useFetchMyApi,因为它违反了钩子规则。
  2. 如何根据 useFetchMyApi 的响应刷新项目列表?
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";

const useFetchMyApi = (location = "") => {
  const [items, setItems] = useState([]);
  useEffect(() => {
    // let's stimulate the API call with a 2 seconds response time
    setTimeout(() => setItems([location, Math.random()]), 5000);
  }, [location]);
  return { items };
};

export default function App() {
  const locationSelect = useRef(null);
  const { items } = useFetchMyApi("mylocation1");
  const onButtonClicked = (event) => {
    const selectedLocation = locationSelect.current.value;
    // Call api and refresh items by location
    // Fix here??
    //useFetchMyApi(selectedLocation);
  };

  return (
    <div className="App">
      <select ref={locationSelect}>
        <option value="location1">Location 1</option>
        <option value="location2">Location 2</option>
      </select>
      <button onClick={onButtonClicked}>Refresh</button>
      <ul>
        {items.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
}

https://codesandbox.io/s/stupefied-water-1o6mz?file=/src/App.js:0-1055

1 个答案:

答案 0 :(得分:4)

问题

<块引用>
  1. 我无法在事件处理程序中调用 useFetchMyApi,因为它违反了钩子规则。

正确,react hooks 只能从功能组件和其他 react hooks 的主体中调用。它们不能有条件地调用,例如在异步回调或循环中。

<块引用>
  1. 如何根据 useFetchMyApi 的响应刷新项目列表?

您在 App 中没有任何内容可以触发重新渲染以触发效果以触发效果的回调。

解决方案

您应该在自定义 useEffect 钩子中利用 useFetchMyApi 钩子的依赖来刷新项目列表。添加一些本地组件状态来表示“当前”选定的位置。使用按钮的 onClick 回调来更新状态并触发重新渲染,然后重新运行您的自定义挂钩。

const useFetchMyApi = (location = "") => {
  const [items, setItems] = useState([]);
  useEffect(() => {
    // let's stimulate the API call with a 2 seconds response time
    setTimeout(() => setItems([location, Math.random()]), 2000);
  }, [location]); // <-- effect callback triggers when location updates
  return { items };
};

function App() {
  const locationSelect = useRef(null);
  const [location, setLocation] = useState("mylocation1"); // <-- add state
  const { items } = useFetchMyApi(location); // <-- pass location value to hook

  const onButtonClicked = () => {
    const selectedLocation = locationSelect.current.value;
    setLocation(selectedLocation); // <-- update state, trigger rerender
  };

  return (
    <div className="App">
      <select ref={locationSelect}>
        <option value="location1">Location 1</option>
        <option value="location2">Location 2</option>
      </select>
      <button onClick={onButtonClicked}>Refresh</button>
      <ul>
        {items.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
}

Edit calling-custom-hook-in-event-handler

替代方案

另一种解决方案是重写您的自定义 useFetchMyApi 钩子,以返回一个回调,以便在点击处理程序中按需调用以执行获取和更新列表。这是一个简单的转换。 注意现在返回的 fetch 函数使用的是位置,而不是钩子调用。

const useFetchMyApi = () => {
  const [items, setItems] = useState([]);

  // custom fetch function consumes location
  const locationFetch = (location = "") => {
    // let's stimulate the API call with a 2 seconds response time
    setTimeout(() => setItems([location, Math.random()]), 2000);
  };
  
  return [items, locationFetch]; // <-- return state and fetch function
};

export default function App() {
  const locationSelect = useRef(null);
  const [items, locationFetch] = useFetchMyApi(); // <-- destructure state and fetch function

  useEffect(() => {
    locationFetch("mylocation1");
  }, []); // <-- initial mounting fetch call

  const onButtonClicked = () => {
    const selectedLocation = locationSelect.current.value;
    locationFetch(selectedLocation); // <-- invoke fetch function
  };

  return (
    <div className="App">
      <select ref={locationSelect}>
        <option value="location1">Location 1</option>
        <option value="location2">Location 2</option>
      </select>
      <button onClick={onButtonClicked}>Refresh</button>
      <ul>
        {items.map((item) => (
          <li>{item}</li>
        ))}
      </ul>
    </div>
  );
}

Edit calling-custom-hook-in-event-handler (forked)