buydadip
buydadip

Reputation: 9407

Strange behaviour with useEffect

I am building a project using react hooks. My useEffect function looks as follows...

useEffect(() => {
  (async function() {
    const queryParams = await qs.parse(props.location.search, { ignoreQueryPrefix: true }).code;
    if (queryParams != undefined) {
      setCode(queryParams);
      postLogin();
    }
  })();
});

The postLogin function is just a GET request to a server using the query parameters defined above. It look as so...

const postLogin = async () => {
  await axios.get("http://XXX.XXX.X.XX:52805/user/auth", {
    params: {
      auth_code: code
    }
  }).then(result => {
    setAuthTokens(result.data.credentials.access_token);
    setLoggedIn(true);
  }).catch(e => {
    setIsError(true);
  });
}

What happens is that the request will get called multiple times when I only want it to get called once.

enter image description here

As you can see in the screenshot above, out of the 5 requests, one of them is successful, the others fail, and I'm not sure what is causing it. Any ideas would be helpful.

Upvotes: 0

Views: 432

Answers (2)

Jack
Jack

Reputation: 658

You might need to add an empty array ( [] ) as the second parameter to your hook. It stops the hook from running multiple times.

The array is supposed to contain variables ( normally part of the component's state ) that the hook should watch and rerun if they change. The variables should be vital for whatever the hook does.

Let's say your hook is supposed to display number of subscribers to a youtube channel in a certain category - let's say the categories are A, B and C (hypothetically) and the user can switch between which category to view may be using a dropdown; then the array would contain the category variable such that whenever it changes, the hook runs to fetch the number of subscribers in that category. Having the array empty is a way of telling React that you have no dependencies and you are ok with what the hook does the first time it runs.

React compares the values in that array for changes on each render and if they don't change, the hook is skipped. If you have it empty, then no value will ever change which means the hook will run once and then be skipped on subsequent rerenders.

Upvotes: 4

Robert Moore
Robert Moore

Reputation: 2572

Your useEffect hook runs on each render. Since you call setIsError or setLoggedIn after the request finishes, this will trigger another render, and another render, etc. Honestly I'm surprised there isn't an infinite loop. To fix this, try making it only attempt to log in when it hasn't attempted before (hasn't had an error and isn't logged in currently.) Like this:

useEffect(async () => {
  if (isLoggedIn || isError) return; // Don't try again if a request has already succeeded or failed.
  // Optionally use "isLoggedIn || isError || isLoading" to prevent multiple concurrent requests,
  // which may cause further re-rendering.
  const queryParams = await qs.parse(props.location.search, { ignoreQueryPrefix: true }).code;
  if (queryParams != undefined) {
    setCode(queryParams);
    postLogin();
  }
});

If you are sure that you only ever want it to run once (on the first render) give it an empty dependency array so it will never run again.

Upvotes: 1

Related Questions