Home > front end >  useEffect is causing an infinite loop but i cant find why. Also what can i improve in the code
useEffect is causing an infinite loop but i cant find why. Also what can i improve in the code

Time:11-02

I made basic blog like project with django rest api and react in frontend but useEffect is causing an infinite loop but i cant find why.I know its the useEffect because it happened few times before while making this project but i was able to fix it but now it has been a bit hard. Also a side question what do u guys think of the code any tip or advice will be appreciated, thanks.

import React, { useState, useEffect } from 'react';
import './App.css';


const App = () => {
  
  const [posts, setPosts] = useState([])
  const [editing, setEditing] = useState(false)
  const [editData, setEditData] = useState([])

  const [create, setCreate] = useState({
    title: "",
    description: "",
    completed: false
  })

  const handleCreate = ((e) => {
    const newData = {...create}
    newData[e.target.id] = e.target.value
    setCreate(newData)
  })


  const handleEdit = ((post) => {
    setCreate({
      title: post.title,
      description: post.description,
    })
    {setEditing(true)}
    {setEditData(post)}
  })

  const handleDelete = ((post) => {
    fetch(`http://127.0.0.1:8000/api/post-delete/${post.id}`, 
      {
        method: 'DELETE',
        headers: {
          'Content-Type': 'application/json',
      }
  })
  console.log(post.id)
})

  const handleSubmit = ((e) => {
    e.preventDefault()
    var url = 'http://127.0.0.1:8000/api/post-create/'

    if (editing == true) {
      url = `http://127.0.0.1:8000/api/post-update/${editData.id}`
    }
    
    fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(create)
    })
    .then(setCreate({
      title: "",
      description: "",
      completed: false
    }))
  })

  useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
  }, [posts])

    return (
      <div className="container">
        <div class="row">
          <div class="col-7">
            <h1 className="p-3 mb-2 bg-dark text-light">The Posts</h1>
            {posts.map((post, index) => {
              return(
                <div key={index} class="list-group-item list-group-item-dark mb-1">
                  <ul>
                    <li>
                      <h3>{post.title}</h3>
                      <p>{post.description}</p>
                      <span class="text-muted">{post.created_at.slice(0, 10)}</span>
                    </li>
                  </ul>
                  <div className="d-grid gap-2 d-md-flex justify-content-md-end">
                    <button onClick={() => handleEdit(post)}  type="button">Edit</button>
                    <button onClick={() => handleDelete(post)}  type="button">-</button>
                  </div>
                </div>
              )
            })}
          </div> 


        <div className="col-5">
          
          <form onSubmit={(e) => handleSubmit(e)} className="mx-2 p-3 rounded border border-2 border-dark">
            <h1 className="p-3 rounded bg-dark text-light">Add New Post</h1>
            
            {/* TITLE */}
            <div style={{ flex: 6 }}>
              <label class="form-label">Title</label>
              <input onChange={(e) =>handleCreate(e)} className="form-control" id="title" value={create.title} type="text" placeholder="Add Post" />
            </div>

            {/* DESCRIPTION */}
            <div class="mb-3">
              <label  class="form-label">Description</label>
              <textarea onChange={(e) =>handleCreate(e)} className="form-control" id="description" value={create.description}  placeholder="Post Description" rows="3"></textarea>
            </div>    
            
            
            <button type="submit" class="btn btn-dark m-3">Submit</button>
          </form>
        </div>

        </div> 
      </div>
    )
  }


export default App;

CodePudding user response:

you are triggering the useEffect function when the state posts is changed and then proceed to update the state in the useEffect function.

better solution:

useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
  }, [])

this will make sure useEffect only runs one time

CodePudding user response:

Remove the posts in dependency array and make it just [].

Here what you need to do

useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
  }, [])

CodePudding user response:

The function inside useEffect will run every time the value of a dependency is updated. You added posts a dependency of the useEffect and updated the value of posts inside the effect using setPosts. And it is causing the infinite loop.

Just remove posts from the dependency array.

useEffect(async () => {
  const response = await fetch('http://127.0.0.1:8000/api/post-list/')
  setPosts(await response.json())
}, [])

CodePudding user response:

useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
}, [posts])

By having posts as a dependency, this means that the useEffect callback will run each time when posts gets updated. By updating posts in the callback, you are causing an infinite loop. To avoid this, you can either remove posts from the dependency array.

It is not good to call your API for the whole list each time you add one entry to your list. You have most of the data already loaded, it would be best to do the following in your handleSubmit function:

    fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(create)
    })
    .then(() => {
      setPosts([...posts, create]);
      setCreate({
        title: "",
        description: "",
        completed: false
      });
    })

You would append your data from the create object, to the already loaded posts array.

CodePudding user response:

This is an infinite loop issue

The problem here is that you trigger the setPosts function inside the useEffect and you trigger the useEffect hook when the value of posts change. Since the setPosts function, changes the value of posts, this is what is causing the loop:

SetPosts trigger=> posts trigger =>UseEffect trigger=> SetPosts and the loop continues.

The best way, is to remove posts from the UseEffect hook, or find another dependency for the useEffect hook as shown below;

  useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
  }, [])

CodePudding user response:

When you update posts using setPosts, this executes useEffect() again. The second argument your passing to useEffect tells it to execute your method everytime [posts] updates, so when you call setPosts(await response.json()) posts updates and your method execute.

You can prevent this with a simple if statement:

useEffect(async () => {
    if(posts.length == 0) {
        const response = await fetch('http://127.0.0.1:8000/api/post-list/')
        setPosts(await response.json())
    }
}, [posts])

In the above code the fetch and following setPosts will only execute when the length of the posts array is zero, once you done the fetch and populates posts with 1 or more items then useEffect still gets fired but doesn't do the fetch.

To illustrate this include a second state value postsFetched and set this to true once you have fetched posts, this prevents the your method provided use useEffect from executing in the first place. Something like:

const [posts, setPosts] = useState([]);
const [postsFetched, setPostsFetched] = useState(false);
...
useEffect(async () => {
    const response = await fetch('http://127.0.0.1:8000/api/post-list/')
    setPosts(await response.json())
    // NOTE: you probably want to qualify that you have fetched posts at this point
    setPostsFetched(true);
}, [postsFetched])

As my comment in the second snippet says you may want to make sure that you have actually fetched some posts before setting postsFetched to true by checking the return from await response.json()

Here's some additional reading:

Or you could just leave the useEffect dependency array empty as others have said.
  • Related