0

I have an app that checks the user id on startup and loads the list of todo items based on the user logged in. I have the useEffect change only when data changes, but I have setData in the body of useEffect() meaning data changes and it re-runs infinitum.

However if I change [data] to [] in the second parameter, then it renders once BUT I have to refresh the page everytime I add a todo item for it to render rather than it render automatically. How can I have it render automatically without looping infinitely?

const [data, setData] = useState([])

useEffect(() => {
    UserService.getUserById(localStorage.getItem("userId")).then(res => {
        if (res.data !== null) {
            setData(res.data.todos)
        }
    })
}, [data])
4
  • Need to show more code. Commented Jan 23, 2021 at 7:02
  • @Jakkie Chan to avoid infinity loop you can check previous data (like count) and then you can setData Commented Jan 23, 2021 at 7:03
  • @buzatto no, I'm not passing in and comparing any objects Commented Jan 23, 2021 at 7:41
  • Why is data in the dependency array? It doesn't appear to be a dependency of the effect. Commented Jan 23, 2021 at 7:58

5 Answers 5

1

You can add a condition in the call back function that checks if a certain condition is met, e.g. if data is empty. If it is empty, then fetch data, otherwise do nothing. This will prevent the infinite loop from happening.


const getData =  useEffect(()=>{
  const fetchData = () => {
    UserService.getUserById(localStorage.getItem("userId"))
     .then(res => {
        if (res.data !== null) {
         setData(res.data.todos)
        }
      })
     .catch(error => {
        // do something with error
      })
  }
  
  if (data.length === 0) 
    fetchData()

},[data]);

Alternatively, you use an empty dependency array so that the callback function in the useEffect is called once.

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

3 Comments

that is equivalent to using [] in the second param, it only renders on refresh and not automatically
where else are you calling setData to update it? If you are not calling setData elsewhere in your code then the component will not "automatically" re-render.
If that's the only place you're calling it then data is never updated and therefore your component will never re-render since the component's state variables never change. How are you adding the todo item?
0

useCallback Hook can be used with slight modifications in your code.

You will need to import useCallback from "react" first.

import {useCallback} from "react";

And then use this useCallback around our getData function. (Have modified the answer a bit)

const getData =  useCallback(()=>{
    UserService.getUserById(localStorage.getItem("userId")).then(res => {
        if (res.data !== null) {
            setData(res.data.todos)
        }
    })
},[data]);


useEffect(() => {
   getData();
}, [data])

This React Hook will make sure that the getData() function is only created when the second argument data changes.

2 Comments

I just tried that, but it still renders it infinitum
Hmm, I see. You have mentioned in your question that I have to refresh the page everytime I add a todo item for it to render rather than it render automatically. . I think that's where we need to improve our code. This is where you actually need to put a handler Function whenever you are may be clicking on add item button . If you can show me your code via codesandbox I would like to have a look and see where the problem actually is :-) Also for a time being instead of using localStorage, you may simply store all the data in a array of objects for demo purpose :-)
0

In your code UserService.getUserById(localStorage.getItem("userId")) return a promise and it get data one time so you just have to call getUserById one time at the time of load by using [] and if you want to call it again make a function and use it wherever on refresh function or on adding todos item or update or delete function. Otherwise you have to use observable or useCallBack hook

Comments

0

You need to pass the reset param to prevent loop. once callback trigger reset value false. so that execution not running again until reset the value

Codesanbox

  export default function App() {
  let i = 1;
  const [data, setData] = useState([]);
  const [reset, setReset] = useState(true);

  useEffect(() => {
    if (reset) {
      setTimeout(() => {
        //callback
        setReset(false);
        setData(Math.random());
      }, 1000);
    }
  }, [data]);

  return (
    <div className="App">
      <h1>{data}</h1>
      <button
        onClick={() => {
          setReset(true);
          setData("");
        }}
      >
        Click this and see the data render again. i just reset the data to empty
      </button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

Comments

0

Use a condition to stop the loop by setting a condition to stop it. You can check if a certain value is set or check if there are any values sent at all.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.