skip to Main Content

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, ie the state changes, ie 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, please help me

2

Answers


  1. You’re absolutely correct in your understanding of why the infinite loop occurs. The issue arises because the todos array is included as a dependency in the useEffect hook, so every time setTodos is called, the todos state changes, triggering the useEffect again, which leads to infinite rerenders.

    Solution:

    Remove todos from the dependency array: For fetching data on the initial mount, you only need an empty dependency array [] so that the effect runs only once when the component is mounted.

    Handle new todos without refreshing: To handle the addition of new todos without needing to refresh the page, you should update the todos state directly after creating a new todo. This way, the new todo will be displayed immediately.

    Here’s how you can adjust your code:

    import { useState, useEffect } from 'react';
    import axios from 'axios';
    
    const TodosComponent = () => {
      const [todos, setTodos] = useState([]);
    
      useEffect(() => {
        const getTodoData = async () => {
          try {
            const { data } = await axios.get(`${BASE_URL}/todos`);
            setTodos(data);
          } catch (error) {
            console.error('Error fetching todos:', error);
          }
        };
        getTodoData();
      }, []); // Empty array ensures this runs only once on mount
    
      const addTodo = async (newTodo) => {
        try {
          const { data } = await axios.post(`${BASE_URL}/todos`, newTodo);
          setTodos((prevTodos) => [...prevTodos, data]); // Update the state with the new todo
        } catch (error) {
          console.error('Error adding new todo:', error);
        }
      };
    
      return (
        <div>
          {/* Render todos and other UI elements */}
        </div>
      );
    };
    
    export default TodosComponent;
    

    Explanation:

    1. useEffect with Empty Dependency Array:
      • By passing an empty array [] as the dependency array, the effect will only run once, when the component is first mounted. This prevents the infinite loop.
    2. addTodo Function:
      • The addTodo function handles adding a new todo. After successfully adding a new todo to the backend, the new todo is appended to the existing todos state using the setTodos function. This allows the new todo to be immediately reflected in the UI without needing to refresh the page.

    Handling Updates or Deletes

    If you need to handle updates or deletes, you can follow a similar approach by updating the todos state accordingly:

    • For updating a todo, find the todo in the state array, modify it, and then set the state with the updated array.
    • For deleting a todo, filter out the todo from the state array and set the state with the filtered array.

    This approach ensures your UI remains in sync with the backend data without the need for page refreshes.

    Login or Signup to reply.
  2. 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 you data updated.

    P.S
    I suggest you to use @tanstack/react-query library to handle this kind of request, it’ll make your code cleaner and easier to work with.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search