0

I am getting an infinite loop and I know the problem is because I am putting in brackets as the second argument the 'posts' and the 'setPost' inside the useEffect function, but I need the page to render whenever I add a new post, so the posts must be in brackets.

function Home() {
   const {userData, setUserData} = useContext(userContext)
   const [posts, setPost] = useState([])
   const [createPost, setCreatePost] = useState('')
   
   const handleToken = () => {
      localStorage.removeItem('auth-token')
   }

const token = localStorage.getItem("auth-token");

const handleOnSubmit = (e) => {
    e.preventDefault()
    axios.post('http://localhost:5000/posts', {textOfThePost: createPost}, {
        headers: { 'auth-token': token },
    })
    .then((res) => {setCreatePost("")})
}

useEffect(() => {
    axios.get('http://localhost:5000/posts')
    .then(res => {
        setPost(res.data)
    })
}, [posts])

return (
    <div className="home">
        <div style={{display: 'flex', alignItems: 'center'}}>
            <h1>this is the home: Welcome, {userData.username}</h1>
            <Link style={{margin: 10}} to="/home">home</Link>
            <Link style={{margin: 10}} to="/profile">profile</Link>
            <Link style={{margin: 10}} onClick={handleToken} to="/">log out</Link>
        </div>
        <form onSubmit={handleOnSubmit}>
            <input type="text" placeholder="What's happening?" value={createPost} onChange={e => setCreatePost(e.target.value)}/>
            <button type="submit">tweet</button>
        </form>
        <div style={{display: 'flex', flexDirection: 'column'}}>
            {posts.map(post => (
                <div style={{border: '2px solid black', marginBottom: 10, marginRight: 'auto', marginLeft: 'auto', width: 300}} key={post._id}>
                    <div style={{display: 'flex', alignItems: 'center'}}>
                    <Avatar src={post.avatar}/>
                    <span style={{color: 'blue', marginLeft: 10}}>{post.name} <span style={{color: 'grey', fontSize: 11}}>@{post?.username}</span></span><br/>
                    </div>
                    <span>{post.textOfThePost}</span><br/>
                    <span>{moment(post.date).format('lll')}</span>
                </div>
            )).reverse()}
        </div>
    </div>
)

}

tadman
  • 208,517
  • 23
  • 234
  • 262
  • As a note you should be able to use `async` and `await` here. – tadman Nov 02 '20 at 22:24
  • It's worth looking at this [related answer](https://stackoverflow.com/questions/59566248/react-useeffect-infinite-loop-fetch-data-axios?rq=1). – tadman Nov 02 '20 at 22:25
  • `posts` shouldn't be in the dependency list, you are not using it in your effect. Most probably, you should update your state after creating your post. – devserkan Nov 02 '20 at 22:29

2 Answers2

1

The problem here is the dependency array for useEffect (and similar hooks) doesn't use deep comparison (for performance reasons).

That is, whenever you get new data via Axios, res.data is a new JavaScript object and as you assign it to state, the effect dependency considers it an entirely changed object and runs the effect again, etc.

The easiest fix is to use a deep-comparing useEffect such as https://github.com/kentcdodds/use-deep-compare-effect .

AKX
  • 152,115
  • 15
  • 115
  • 172
0

You are not using posts in your effect, so it shouldn't be in your dependency array at all. One solution for your issue might be getting the posts in the first render, just once, then whenever you create a post, using the response updating the posts state.

const posts = [
  { id: "1", text: "foo" },
  { id: "2", text: "bar" },
  { id: "3", text: "baz" }
];

const API = {
  getPosts: () =>
    new Promise((resolve) => setTimeout(() => resolve(posts), 2000)),
  createPost: () =>
    new Promise((resolve) =>
      setTimeout(() => resolve({ id: "3", text: "fizz" }), 1000)
    )
};

function Posts() {
  const [posts, setPosts] = React.useState([]);

  React.useEffect(() => {
    API.getPosts().then(setPosts);
  }, []);

  function handleOnSubmit() {
    API.createPost().then((res) => setPosts((prev) => [...prev, res]));
  }

  return (
    <div>
      <div>
        <button onClick={handleOnSubmit}>Create Post</button>
      </div>
      {!Boolean(posts.length) ? (
        <span>Loading posts...</span>
      ) : (
        posts.map((post) => <div>{post.text}</div>)
      )}
    </div>
  );
}

ReactDOM.render(
  <Posts />,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root" />

Don't bother the API part, I'm just mimicking your requests. The important part is after creating the post, using the response, and updating the state.

devserkan
  • 16,870
  • 4
  • 31
  • 47