2

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)

4
  • The useDataApi hook is incomplete could you add in the arguments that it takes i.e. the top part of the function is missing. Commented Mar 27, 2021 at 5:22
  • Is the new data from the dynamic URL is correct? If so, you can new data, update the state and the component does not re render? Also the URL state is not needed just use it for the axios without setting the state again for no reason Commented Mar 27, 2021 at 5:29
  • @yudhiesh Sorry, I just updated the code snippet to show the parameters of the useDataApi. It has 3 parameters that I pass in for state, errors, useEffect refresh. Commented Mar 27, 2021 at 5:30
  • @MichaelParkadze Thanks for the response, I just updated a few parameters that were missing from the useDataApi in the code snippet above. The new data from the url is correct, but the useEffect hook doesn't see that until I use setUrl inside the function. BUT, for whatever reason, no matter where inside the function i put the setUrl, the useEffect only updates the url AFTER it has already started the data fetch again (with the old url). If i don't add the setUrl inside the useEffect, I can't seem to get the useEffect to pick up on the change. Does that make sense? Commented Mar 27, 2021 at 5:36

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

1 Comment

I see! I am still a bit behind on fully grasping useState... I thought I knew, but clearly not! I ended up just taking out the setUrl inside of the useEffect as it was useless I believe? Just to be clear, you recommend me keeping the url useState and simply changing the name from initialUrl to something more practical correct? Thanks so much again! I hope that I at least structured it correctly! Thanks for your help!
0

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?

3 Comments

This did work! Previously, I tried to use another useEffect in my main component, but I could not get that to work for many reasons. I see how it works, but I guess I don't know why? I am still pretty new to React, but just unsure why it wouldn't catch the NEW initialUrl when it changed? Also, if I put a setUrl inside of the original useEffect it also would not pick up on it, not sure why tho? This is great! I really appreciate it! But you said there was a better way to write the code overall? suggestions? I want to duplicated this process many times probably!
it looks like you want to use the effect parameter as the trigger for calling the api, but you're also relying on the url being able to change, which likely should also trigger the api call? If so then my first suggestion is better, and you should change your initial code to remove the state hook for url, and have your single effect watch both effect and url. Further, It also might be better to kill off the effect parameter altogether and just watch for a change in url?
Understood! That makes total sense now! Thank you for your help! It really means a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.