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

10

我有一个名为useFetchMyApi的自定义hook,它封装了对API端点的fetch请求。该函数钩子接受一个参数并包含在post请求体中。数据数组输出取决于钩子参数。

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

我的问题:

  1. 我无法在事件处理程序中调用useFetchMyApi,因为这违反了hook的规则。
  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


为什么你不将这些项声明为 App 状态? - thelonglqd
@thelonglqd 这个项目列表应该由自定义钩子来管理,对吧? - user2176499
你不能这样做,你需要修改钩子以返回执行获取操作的函数,现在你的钩子依赖于位置参数,而不是 onClick 事件。 - jean182
1个回答

17

问题

  1. 我无法在事件处理程序中调用 useFetchMyApi ,因为它违反了hook规则。

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

  1. 如何刷新依赖于 useFetchMyApi 响应的项目列表?

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

解决方案

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

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函数使用位置而不是hook调用。

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)


1
非常感谢您提供的详细而有用的解释。状态驱动UI,而不是组件/钩子。我的错误在于认为钩子可以直接影响其他组件/钩子。 - user2176499

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接