1

Hope anyone is able to help me with a custom react hook.

My custom react hook "useFetch" is running 8 times when called. Can anyone see, why it is running 8 times when the custom "useFetch" hook is called?

I am a bit new to React, but it seems like I am using useEffect method wrong. Or maybe I need to use another method.

UseFetch hook method:

import React, { useState, useEffect } from "react";

export const useFetch = function (
  options = {
    IsPending: true,
  },
  data = {}
) {

  // load data
  const [loadData, setLoadData] = useState(null);
  // pending
  const [isPending, setIsPending] = useState(false);
  // error
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    // method
    const fetchData = async function () {
      // try
      try {
        // set pending
        setIsPending(true);

        // response
        const response = await fetch(data.url, data);

        // handle errors
        if (response.status !== 200 && response.status !== 201) {
          // throw new error with returned error messages
          throw new Error(`Unable to fetch. ${response.statusText}`);
        }

        // convert to json
        const json = await response.json();
        // set load data
        setLoadData(json);

        // set error
        setIsError(false);
        // set pending
        setIsPending(false);

        // catch errors
      } catch (err) {
        // set error
        setIsError(`Error fetching data: ${err.message}`);
        // set pending
        setIsPending(false);
      }
    };

    // invoke fetch data method
    fetchData();
  }, []);
  // return
  return {
    loadData,
    isPending,
    isError,
  };
};

export default useFetch;
Qais Wardag
  • 63
  • 2
  • 9

2 Answers2

2

Everytime you change a state in a hook, the component that has the hook in it will rerender, making it call the function again. So let's start counting the renders/rerenders by the change of state:

  1. Component mounted
  2. setIsPending(true)
  3. setLoadData(json)
  4. setIsPending(false) (depending if it's successful or not you might get more state changes, and therefore rerenders, and therefore hook being called again)

So 4 is not 8, so why are you getting 8?

I presume you are using React18, and React18 on development and StrictMode will call your useEffect hooks twice on mount: React Hooks: useEffect() is called twice even if an empty array is used as an argument

What can you do to avoid this?

First of all, check on the network tab how many times you are actually fetching the data, I presume is not more than 2.

But even so you probably don't want to fetch the data 2 times, even though this behaviour won't be on production and will only be on development. For this we can use the useEffect cleanup function + a ref.

const hasDataFetched = useRef(false);

useEffect(() => {
  // check if data has been fetched
  if (!hasDataFetched.current) {
    const fetchData = async function () {
      // fetch data logic in here
    };
    fetchData();
  }
  // cleanup function
  return () => {
    // set has data fetched to true
    hasDataFetched.current = true;
  };
}, []);

Or as you suggested, we can also add data to the dependency array. Adding a variable to a dependency array means the useEffect will only be triggered again, when the value of the variable inside the dependency array has changed.

(Noting that data is the argument you pass to the useFetch hook and not the actual data you get from the fetch, maybe think about renaming this property to something more clear).

useEffect(() => {
  // check if data has been fetched

    const fetchData = async function () {
      // fetch data logic in here
    };
    fetchData();

}, [data]);

This will make it so, that only if loadData has not been fetched, then it will fetch it. This will make it so that you only have 4 rerenders and 1 fetch.

(There is a good guide on useEffect on the React18 Docs: https://beta.reactjs.org/learn/synchronizing-with-effects)

Chris
  • 76
  • 4
  • 1
    Thanks so much for your help - I learned a lot from your feedback. Yes, I was using StrictMode. – Qais Wardag Dec 11 '22 at 19:24
  • Using a ref with an `if` statement in `useEffect` should be avoided as much as possible. See this thread: https://stackoverflow.com/questions/72238175/why-useeffect-running-twice-and-how-to-handle-it-well-in-react – Youssouf Oumar Dec 11 '22 at 19:50
0

Every time you change the state within the hook, the parent component that calls the hooks will re-render, which will cause the hook to run again. Now, the empty array in your useEffect dependency should be preventing the logic of the hook from getting called again, but the hook itself will run.

Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
  • Hi Based on the hook, what should I insert in the empty array? I am calling the method here: https://gist.github.com/qaiswardag/35ff8d52b951b37e199029a6af5ab4e4 – Qais Wardag Dec 10 '22 at 13:42
  • 1
    If you wanted the hook to be fully correct, the dependency array should be listing `data.url`. Otherwise it's totally fine. I know the hook "running" eight times is strange but that's actually expected. Your logic within the hook will properly one run once – Matthew Herbst Dec 11 '22 at 08:16