简体   繁体   中英

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. So, I do what anyone would when trying to use React, and I am passing dynamic URL parameters into my API request (pictured below).

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).

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.

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!

(I am using React-Router to route the single component to this link)

Ok, I think there are 2 little issues in your code.

Inside the parent function

This is my main function that is going to use your custom hook. 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.

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. Remember that every time you call the setState function is not synchronous. 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. To know more about it, you can read this: 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. I added some comments to your code //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. 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 .

This isn't great.

So another option would be to keep your url state and add a second useEffect that watches 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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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