1

can someone help explain why this code is causing an infinite loop and what is the best approach to fixing it please and thank you.
I am assuming its because of the useEffect within the App component which is causing a re-render which then the useState also causes a render therefore causing an infinite loop. I think I am not understanding how useEffect and useState works properly.

import "./styles.css";
import React, { useEffect, useState } from "react";

interface IObject {
  isLoading: boolean;
  isError: boolean;
  data: any[] | any;
}

function useHook1(): IObject {
  console.log("hook 1 too many re-renders?");
  return { isLoading: false, isError: false, data: [] };
}

function useHook2(): IObject {
  const result = { isLoading: false, isError: false, data: "testing" };
  console.log("hook 2 too many re-renders?");
  return result;
}

export default function App() {
  const { isLoading, isError, data } = useHook1();
  const testResult = useHook2();
  const [state, setState] = useState();

  useEffect(() => {
    console.log("inside useEffect within App")
    setState(testResult.data)
  }, [testResult])

  console.log("too many re-renders?");
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <p>{testing}</p>
    </div>
  );
}

1 Answer 1

2

useHook2 returns a new object every time it runs. This means testResult is new/changed on every render, and your useEffect runs whenever testResult changes. So:

  1. Your effect updates state, which causes a re-render.

  2. On the rerender, useHook2 gets invoked and testResult is updated.

  3. testResult changed, so your effect runs again and you return to step 1, entering an infinite loop.


The solution, if I understand what you're trying to do, is to do the state management in the custom hook:

function useHook1() {
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [data, setData] = useState([])

  return {
    isLoading,
    isError,
    data,
  };
}

You can simplify this a bit by collapsing the repeated useState usage into a single useReducer:

function useHook1() {
  const [state, dispatch] = useReducer(
    (state, action) => ({...state, ...action}),
    { isLoading: false, isError: false, data: [] }
  );

  return { ...state };
}

If you need to trigger updates from outside the hook you can make that available too:

function useHook1() {
  const [state, dispatch] = useReducer(
    (state, action) => ({...state, ...action}),
    { isLoading: false, isError: false, data: [] }
  );

  return { ...state, dispatch };
}

export default function App () {
  const [{ isLoading, isError, data }, setState] = useHook1();
  
  return isLoading ? <div>Loading</div> : (
    <div onClick={() => dispatch({ data: ["new data"]})}>...</div>
  )
}
Sign up to request clarification or add additional context in comments.

3 Comments

So based on what you said, i am going to assume that useHook1 is going to do exactly the same thing (cause an infinite loop) when I put the variable "data" that I destrcuted out of it in the dependency array within useEffect in app component. So how can i fix this issue? do i put the value in state in the custom hooks and return that state? i would assume this would cause the custom hook to render as well therefore still not resolving my issue. What do you think is a good way of attacking this issue? assuming that data can change in the custom hook
Updated answer with a possible solution. I'm in the middle of something right now but I'll try to come back and clarify a few things in a bit.
Updated with a couple clarifications.

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.