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])
Jakkie Chan
  • 35
  • 2
  • 8

5 Answers5

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.

Lawynn Jana
  • 88
  • 1
  • 5
  • that is equivalent to using `[]` in the second param, it only renders on refresh and not automatically – Jakkie Chan Jan 23 '21 at 07:33
  • 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. – Lawynn Jana Jan 23 '21 at 07:47
  • 1
    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? – Lawynn Jana Jan 23 '21 at 08:05
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.

Imran Rafiq Rather
  • 7,677
  • 1
  • 16
  • 35
  • I just tried that, but it still renders it infinitum – Jakkie Chan Jan 23 '21 at 07:35
  • 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 :-) – Imran Rafiq Rather Jan 23 '21 at 09:11
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

Muhammad Usman
  • 361
  • 3
  • 16
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>
  );
}
prasanth
  • 22,145
  • 4
  • 29
  • 53
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.

Moataz Alsayed
  • 381
  • 2
  • 12