[SOLVED] State variable change causes an infinte render loop

Issue

I’m attemptign to create a comment section using react. The component pulls all of the comments and replies to each comment in one large dataset using an outer join, then it copies the results into two seperate arrays (comment array and replies array)

However, when I try to set the comment array to a state variable the component infinitely re-renders itself.

I’ve included a comment where the problem occurs. Un-commenting the line below it causes the program to work normally, but the map function has nothing to display

import React, {useState, useEffect} from 'react'
import Axios from 'axios';
import {Form, Button} from 'react-bootstrap';
import Comment from './Comment';
import { render } from 'react-dom';

function Thread(props) { 
    const {threadID} = props;
    const [commentBody, setCommentBody] = useState('');
    const [replyArray, setReplyArray] = useState([]);
    const [commentArray, setCommentArray] = useState([]);
    const [comments, setComments] = useState([]);
    let commentResponse = [];
      useEffect(() => {
      Axios.get(`http://localhost:3001/api/selectcomments/${threadID}`).then((res) => {
      commentResponse = res.data;
    });
  }, [threadID]);

let tempReplies = [];
let tempComments = [];
for(let i = 0 ; i < commentResponse.length; i++)
  {
    tempComments.push({body: comments[i].body, commentID: comments[i].commentID})
    tempReplies.push({replyBody: comments[i].replyBody, commentID: comments[i].commentID})
  }

  let filteredArray = [...new Map(tempComments.map(x => [x['commentID'], x])).values()]
  console.log("splitArray called")


  //CAUSES INFINITE RENDER LOOP
  setCommentArray(filteredArray);

 return (
<section className="comment-wrapper">
      <div className="comment-section">
        <h2>Comments</h2>
        <Form>
          <Form.Control
            as="textarea"
            rows={3}
            placeholder="Leave a Comment"
            onChange={(val) => {
              setCommentBody(val);
            }}
          />
        </Form>
        <Button
          // all elements in the comment section have a default margin of 1rem, but this looks strange when applied to this button
          style={{ "margin-top": "0rem" }}
          type="submit"
          variant="primary"
        >
          Post
        </Button>
        
        {
        commentArray.map((data) => {
          
          console.log('map function for thread fired');
          console.log(commentArray)

          const {commentID, body} = data;
          
          const replies = replyArray.filter(x => x.commentID == commentID)
          return (
            <>
              <Comment username="User1" body={body} commentId = {commentID}/>

              {
              
              
              replies.map((replyData)=>{ 
              if(!(replyData.replyBody == null))
              {
              return(
                
              <div className="reply">
              <h3>Username</h3>
              <p>{replyData.replyBody}</p>
              </div>
              )
              }
              
              }
              )}
              
              
            </>
          );
        })}
      </div>
    </section>

)
}

export default Thread

Solution

You’ve got an issue because you’re unconditionally setting state every time your component re-renders; resulting in yet-another re-render.

In situations like this you need to step back and think about when you actually need your UI to redraw itself. In this case with the code as it stands, it’s very simple – when some data has come back from the server API.

You’ve already got something running in the then() of the Axios.get call in your useEffect. Here is actually the ideal place to do all the array slicing-and-dicing before setting the state, which will cause the re-draw:

useEffect(() => {
    Axios.get(
      `http://dummy.restapiexample.com/api/v1/employee/${threadID}`
    ).then((res) => {
      commentResponse = res.data;
      let tempReplies = [];
      let tempComments = [];
      for (let i = 0; i < commentResponse.length; i++) {
        tempComments.push({
          body: comments[i].body,
          commentID: comments[i].commentID
        });
        tempReplies.push({
          replyBody: comments[i].replyBody,
          commentID: comments[i].commentID
        });
      }

      let filteredArray = [
        ...new Map(tempComments.map((x) => [x["commentID"], x])).values()
      ];
      console.log("splitArray called");

      // This should now work fine - no infinite looping
      setCommentArray(filteredArray);
    });
  }, [threadID]);

Answered By – millhouse

Answer Checked By – Willingham (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.