skip to Main Content

Why this code makes infinite loop in react. here the useEffect with data and columnDataWithTaskProperty cause the infinite loop but i don’t know why


const useBoard = () => {
  const { data: columnData } = useGetColumnQuery();
  const { data } = useGetTaskAccordingToStatus();
  const [columnDataWithTaskProperty, setColumnDataWithTaskProperty] = useState(
    []
  );

  const [finalState, sFinalState] = useState([]);

  useEffect(() => {
    const allColumns = columnData?.data?.map((item) => ({
      name: item.name,
      _id: item._id,
      tasks: [],
    }));
    setColumnDataWithTaskProperty(allColumns);
  }, [columnData]);

  useEffect(() => {
      console.log("meeee data deps");
      const updatedColumns = columnDataWithTaskProperty.map((column) => ({
        ...column,
        tasks: data.flat().filter((task) => task.status === column.name),
      }));
      sFinalState(updatedColumns);
  }, [columnDataWithTaskProperty, data]);

  return {
    finalState,
  };
};

here is how i’m fetching the data using useGetTaskAccordingToStatus. for fetching data i’m using the react query.

const useGetTaskAccordingToStatus = () => {
  const { active_project } = useSelector(projectDataInStore);

  const userQueries = useQueries({
    queries: statesOfTaskManager.map((status) => {
      return {
        queryKey: [status, active_project],
        queryFn: () =>
          customAxiosRequestForGet("/task", {
            status,
            projectName: active_project,
          }),
        onSuccess: ({ data }) => {
          return data;
        },
      };
    }),
  });

  const data = userQueries?.map((item) => item?.data?.data);

  return { data };
};

3

Answers


  1. I suggest you using only one use effect and remove columnDataWithTaskProperty for testing sack, you may return it later on.
    here’s an example of my suggestion, but can’t test it without actual data

    const useBoard = () => {
      const { data: columnData } = useGetColumnQuery();
      const { data } = useGetTaskAccordingToStatus();
      const [finalState, sFinalState] = useState([]);
    
      useEffect(() => {
        const allColumns = columnData?.data?.map((item) => ({
          name: item.name,
          _id: item._id,
          tasks: [],
        }));
          console.log("meeee data deps");
          const updatedColumns = allColumns.map((column) => ({
            ...column,
            tasks: data.flat().filter((task) => task.status === column.name),
          }));
          sFinalState(updatedColumns);
      }, [columnData, data]);
    
      return {
        finalState,
      };
    };
    

    could you please examine the changes on "check" in this:

    const useGetTaskAccordingToStatus = () => {
        const { active_project } = useSelector(projectDataInStore);
        const { check, setCheck } = useState(false);
    
        useEffect(() => {
            console.log(check);
        }, [check])
    
        const userQueries = useQueries({
            queries: statesOfTaskManager.map((status) => {
                setCheck(false);
                return {
                    queryKey: [status, active_project],
                    queryFn: () =>
                        customAxiosRequestForGet("/task", {
                            status,
                            projectName: active_project,
                        }),
                    onSuccess: ({ data }) => {
                        setCheck(true);
                        return data;
                    },
                };
            }),
        });
    
        const data = userQueries?.map((item) => item?.data?.data);
    
        return { data };
    };
    
    Login or Signup to reply.
  2. Haven’t tested this method but I think using an object that is not instantiated as a useState causes this kind of issues when used as a dependency on a useEffect. Using the object as a useState object helps React’s useEffect keep track of the changes and prevents those kind of issues.

    Try this solution:

    // Declare data as a useState object
    const [data, setData] = useState({})
    
    // Instantiate data on first render
    useEffect(() => {
      useGetTaskAccordingToStatus(setData)
    }, [])
    
    // Pass setState to function
    const useGetTaskAccordingToStatus = (setData) => {
        const { active_project } = useSelector(projectDataInStore);
    
        const userQueries = useQueries({
            queries: statesOfTaskManager.map((status) => {
                setCheck(false);
                return {
                    queryKey: [status, active_project],
                    queryFn: () =>
                        customAxiosRequestForGet("/task", {
                            status,
                            projectName: active_project,
                        }),
                    onSuccess: ({ data }) => {
                        setCheck(true);
                        return data;
                    },
                };
            }),
        });
    
        setData(userQueries?.map((item) => item?.data?.data));
    
    };
    
    
    Login or Signup to reply.
  3. The problem you are encountering is actually a problem in useGetTaskAccordingToStatus. This hook returns a new object each render, therefore triggering the effects each time.

    The solution should be to wrap the map inside that hook in useMemo:

    const useGetTaskAccordingToStatus = () => {
      const { active_project } = useSelector(projectDataInStore);
    
      const userQueries = useQueries({
        queries: statesOfTaskManager.map((status) => {
          return {
            queryKey: [status, active_project],
            queryFn: () =>
              customAxiosRequestForGet("/task", {
                status,
                projectName: active_project,
              }),
            onSuccess: ({ data }) => {
              return data;
            },
          };
        }),
      });
    
      const data = useMemo(()=>userQueries?.map((item) => item?.data?.data), [userQueries]);
    
      return { data };
    };
    

    Assuming useQueries returns a stable array (only changing it when the query results change), the useMemo should fix your problem by only running the map when the data actually changes.

    Additionally, useMemo is actually the correct hook to use in your useBoard hook instead of useEffect. You want to calculate something based on the state, not synchronize with an external system.

    This has the benefit of calculating the values directly when the dependencies change, useEffect only runs after render, so the old hook with useEffect should actually cause multiple rerenders when the data changes, the useMemo variant only one rerender.

    const useBoard = () => {
      const { data: columnData } = useGetColumnQuery();
      const { data } = useGetTaskAccordingToStatus();
    
    
      const columnDataWithTaskProperty = useMemo(() => {
        return columnData?.data?.map((item) => ({
          name: item.name,
          _id: item._id,
          tasks: [],
        }));
      }, [columnData]);
    
      const finalState = useMemo(() => {
          console.log("meeee data deps");
          return columnDataWithTaskProperty.map((column) => ({
            ...column,
            tasks: data.flat().filter((task) => task.status === column.name),
          }));
      }, [columnDataWithTaskProperty, data]);
    
      return {
        finalState,
      };
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search