reactjsreact-hooks

Infinite re-renders in react due to useEffect hook


I used useState hook for a todos array

const [todos, setTodos] = useState([]);

I'm using this array to display and index of all todos. I also need the todos for the initial mount, so I'm using useEffect hook.

useEffect(() => {
  const getTodoData = async () => {
    const { data } = await axios.get(`${BASE_URL}/todos`);
    setTodos(data);
  };
  getTodoData();
}, [todos]);

However, the above code generates infinite rerenders and infinite requests are made to ${BASE_URL}/todos.

I understand why this is happening. Correct me if I'm wrong but I think when I setTodos(data); the todos array changes, i.e. the state changes, i.e. the dependency in the useEffect hook [todos] changes. Due to this, the setup function runs, it makes the request, and setTodos(data); again. Hence the infinite loop.

I saw other posts related to this, and almost everyone was saying to just remove the dependency. But what I don't understand is if we were to remove the dependency then after making a new todo, we would have to refresh the page to see the changes reflected. I don't think that's feasible.

If there is a solution to making the new todo appear on the page without refreshing.


Solution

  • I think you're missing a step and that was causes you that issue.

    You have an array of todos and that's great, with that array you can display your todos.

    Now comes the part of thinking - when would the array change? I can think of this scenarios:

    1. Initial loading of todos
    2. Adding new todo
    3. Removing an existing todo

    The step that you're missing, is separating these scenarios. You would like to get something like this:

    const [todos, setTodos] = useState([]);
    
    useEffect(() => {
       const getTodoData = async () => {
         const { data } = await axios.get(`${BASE_URL}/todos`);
         setTodos(data);
       };
       getTodoData();
    }, []); // This use effect will do scenario 1
    
    // Use this function to create new todo
    const addTodo = (newTodo) => {
      const { data } = await axios.post(`${BASE_URL}/todos`, { newTodo });
      
      // assuming the returned data is all todos from the backend
      setTodos(data);
    }
    
    // Use this function to remove existing todo
    const removeTodo = (existingTodoId) => {
      const { data } = await axios.delete(`${BASE_URL}/todos/${existingTodoId}`);
    
      // assuming the returned data is all todos from the backend
      setTodos(data);
    }
    

    Doing this way you won't encounter the issue of infinite rerenders and also you keep your data updated.