简体   繁体   English

使用 useEffect 挂钩获取数据时,React 组件不会在 URL 参数更改上重新渲染

[英]React component not re-rendering on URL parameter change when using useEffect hook to fetch data

Here's the issue:这是问题:

I have a component that is meant to be the same structure for ≈ 25 different items/pages.我有一个组件意味着≈ 25不同的项目/页面具有相同的结构。 So, I do what anyone would when trying to use React, and I am passing dynamic URL parameters into my API request (pictured below).所以,当我尝试使用 React 时,我会做任何人都会做的事情,并且我将动态 URL 参数传递到我的 API 请求中(如下图所示)。

const [{ items, isLoading, isError }] = useDataApi(
`http://localhost:5000/api/teams/topspending/${params.team}`,
[],
params.team);

This is simply using a useEffect component that has been separated for simplicity (pictured below).这只是使用为简单起见而分离的useEffect组件(如下图所示)。

const useDataApi = (initialUrl, initialData, effect) => {
  console.log("start/top Data API");
  const [items, setItems] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {

    setUrl(initialUrl);

    const abortCtrl = new AbortController();
    const opts = { signal: abortCtrl.signal };

    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      console.log("data loading");

      try {
        console.log(url, "this should be the new Url");
        const result = await axios(url, opts);

        setItems(result.data.data);
      } catch (error) {
        setIsError(true);
      }
      console.log("data loaded...");
      setIsLoading(false);
    };

    fetchData();
    return () => abortCtrl.abort();
  }, [effect]);

  return [{ items, isLoading, isError }];
};

export default useDataApi;

The task is pretty simple.任务很简单。 Upon clicking on a simple navigation link in the navbar to change the URL from http://localhost:5000/api/teams/topspending/Team1 to http://localhost:5000/api/teams/topspending/Team2 I am wishing that the SAME component will re-render after fetching NEW data with the UPDATED URL. Upon clicking on a simple navigation link in the navbar to change the URL from http://localhost:5000/api/teams/topspending/Team1 to http://localhost:5000/api/teams/topspending/Team2 I am wishing that SAME 组件将在使用更新的 URL 获取新数据后重新渲染。

I have tried many things... and I can get the component to update after every URL change, BUT the data fetched is still the OLD data!我已经尝试了很多事情......每次 URL 更改后,我都可以更新组件,但获取的数据仍然是旧数据!

(I am using React-Router to route the single component to this link) (我正在使用 React-Router 将单个组件路由到此链接)

Ok, I think there are 2 little issues in your code.好的,我认为您的代码中有两个小问题。

Inside the parent function在父 function

This is my main function that is going to use your custom hook.这是我的主要 function 将使用您的自定义挂钩。 If you see, I don't use interpolation because it is not going to be detected by your custom hook.如果您看到,我不使用插值,因为您的自定义钩子不会检测到它。 That is why your initialUrl variable (Your URL) in your custom hook never change.这就是为什么您的自定义挂钩中的initialUrl变量(您的 URL)永远不会改变的原因。

const App = () => {
  const [id, setId] = React.useState(1);
  const response = useDataApi(
    'https://jsonplaceholder.typicode.com/posts/' + id,
    [],
    id,
  );

  return (
    <>
      <div>My id {id}</div>
      <button onClick={() => setId(id + 1)}>Click Me!</button>
    </>
  );
};

Inside the custom hook自定义钩子内部

It seems to me that you are misunderstanding the setState function provided by react.在我看来,您误解了 react 提供的setState function。 Remember that every time you call the setState function is not synchronous.请记住,每次调用setState function 时都不是同步的。 I mean, if you use setUrl(initialUrl) , then in the next line of code your state variable url will not necessarily have the values already updated.我的意思是,如果您使用setUrl(initialUrl) ,那么在下一行代码中,您的 state 变量url不一定已经更新了值。 To know more about it, you can read this: https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous要了解更多信息,您可以阅读以下内容: https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous

I would suggest using another variable to call the correct URL and change the variable names of your custom hook.我建议使用另一个变量来调用正确的 URL 并更改自定义挂钩的变量名称。 I added some comments to your code //Note:我在您的代码中添加了一些注释//Note:

export const useDataApi = (initialUrl, initialData, effect) => {
  console.log("start/top Data API", effect, initialUrl);
  const [items, setItems] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    // Note: This not sync function
    setUrl(initialUrl);

    const abortCtrl = new AbortController();
    const opts = { signal: abortCtrl.signal };

    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      console.log("data loading");

      try {
        console.log(url, "this should be the new Url");
        // Note: I changed this url variable of your state, to use your initialUrl variable. (this initialUrl parameter should have your UPDATED URL)
        const result = await axios(initialUrl, opts);

        setItems(result.data);
      } catch (error) {
        setIsError(true);
      }
      console.log("data loaded...");
      setIsLoading(false);
    };

    fetchData();
    return () => abortCtrl.abort();
  }, [effect]);

  return [{ items, isLoading, isError }];
};

I Hope, this can help you..我希望,这可以帮助你..

setState is asynchronous, so there's no guarantee as to when it will be affected before the next render. setState 是异步的,因此无法保证在下一次渲染之前它何时会受到影响。 There's multiple ways to rewrite your code to work more predictably, but with the examples you've provided the easiest solution is to remove the url state altogether and just use initalUrl in your call to axios .有多种方法可以重写代码以更可预测地工作,但是对于您提供的示例,最简单的解决方案是完全删除 url state 并在对initalUrl的调用中使用axios

This isn't great.这不是很好。

So another option would be to keep your url state and add a second useEffect that watches url .因此,另一种选择是保留您的url state 并添加第二个useEffect观察url

eg.例如。

const useDataApi = (initialUrl, initialData, effect) => {
  console.log("start/top Data API");
  const [items, setItems] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    setUrl(initialUrl);
  }, [effect]);

  useEffect(() => {
    const abortCtrl = new AbortController();
    const opts = { signal: abortCtrl.signal };

    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      console.log("data loading");

      try {
        console.log(url, "this should be the new Url");
        const result = await axios(url, opts);

        setItems(result.data.data);
      } catch (error) {
        setIsError(true);
      }
      console.log("data loaded...");
      setIsLoading(false);
    };

    fetchData();
    return () => abortCtrl.abort();

  }, [url])

  return [{ items, isLoading, isError }];
};

export default useDataApi;

Still not great, but does what you're trying to do?仍然不是很好,但你想做的事吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM