Home > Software design >  React Component is rendering infinitely when passing data in between sibling components
React Component is rendering infinitely when passing data in between sibling components

Time:12-20

My component structure is like this

App 
| - TopHeader
| - Main
|- Thread
| - Modal

Topheader contains a button for opening a Modal which has a textbox. I'm passing textbox data from Modal->App and changing newPost value so that Main could re-render upon detecting change. But this is causing somehow infinite re-rendering. Here is my code for this. App:

import "./scss/App.scss";
import Sidebar from "./components/Sidebar";
import TopHeader from "./components/TopHeader";
import Main from "./components/Main";
import RightPan from "./components/RightPan";
import Modal from "./components/Modal";
import React, { useEffect, useState } from "react";

function App() {
  const [shouldModalPopUp, setShouldModalPopUp] = useState(false);
  const [newPost, setNewPost] = useState();
  function handleNewThreadButtonClick() {
    setShouldModalPopUp(true);
  }
  function handleCloseButtonClick() {
    setShouldModalPopUp(false);
  }

  function onPostSubmitHandler(newPost) {
    console.log("before preventDefault");
    setNewPost(newPost);
    handleCloseButtonClick();
  }

  return (
    <div className="App">
      {/* <Sidebar key={1} /> */}
      <TopHeader
        newThreadButtonClickHandler={handleNewThreadButtonClick}
        key={2}
      />
      <Main newPost={newPost} key={3} />
      {/* <RightPan key={4} /> */}
      <Modal
        shouldModalPopUp={shouldModalPopUp}
        handleCloseButtonClick={handleCloseButtonClick}
        onPostSubmitHandler={onPostSubmitHandler}
        key={5}
      />
    </div>
  );
}

export default App;

Main

import "../scss/App.scss";
import Thread from "./Thread";
import React, { useEffect, useState } from "react";

function Main(props) {
  const [allThreads, setAllThreads] = useState([]);

  function onPostSubmitHandler(newPost) {
    setAllThreads((prevState) => {
      const newList = [];
      for (let i = 0; i < prevState.length; i  ) {
        newList.push(prevState[i]);
      }
      newList.push({ post: newPost, id: Math.random() });
      console.log("previous state length: "   prevState.length);
      console.log("new state length: "   newList.length);
      return newList;
    });
  }

  console.log("props received in Main"   JSON.stringify(props));
  function onDeleteHandler(id) {
    console.log("inside ondeletehandler "   id);
    const newList = [];
    for (let i = 0; i < allThreads.length; i  ) {
      if (allThreads[i].id != id) {
        newList.push(allThreads[i]);
      }
    }
    setAllThreads(newList);
  }

  if (props.newPost) {
    onPostSubmitHandler(props.newPost);
  }

  return (
    <div className="main">
      <span>main</span>
      {allThreads.map((thread) => {
        console.log("appending posts in list");
        console.log(thread.post);
        return (
          <Thread
            post={thread.post}
            id={thread.id}
            onDeleteThread={onDeleteHandler}
          />
        );
      })}
    </div>
  );
}

export default Main;

Modal

import React, { useState } from "react";

function Modal(props) {
  const [post, setPost] = useState("");

  function submitHandler(event) {
    event.preventDefault();
    props.onPostSubmitHandler(post);
  }

  function onChangeHandler(event) {
    event.preventDefault();
    setPost(event.target.value);
  }

  if (props.shouldModalPopUp) {
    return (
      <div>
        <div className="backdrop" />
        <div className="modal">
          <form onSubmit={submitHandler}>
            <textarea
              id="txtArea"
              className="postTextArea"
              placeholder="please type your post.."
              rows="20"
              cols="80"
              onChange={onChangeHandler}
            />
            <br />
            <br />

            <button
              type="button"
              className="button"
              onClick={props.handleCloseButtonClick}
            >
              close
            </button>
            <button type="submit" className="button">
              Submit
            </button>
          </form>
        </div>
      </div>
    );
  }
  return null;
}
export default Modal;

TopHeader

import "../scss/App.scss";

function TopHeader(props) {
  return (
    <div className="topHeader">
      <button
        className="button button--newthread"
        onClick={props.newThreadButtonClickHandler}
      >
        Start a new Thread
      </button>
    </div>
  );
}
export default TopHeader;

Thread

import { useState } from "react/cjs/react.development";
import "../scss/App.scss";
function Thread(props) {
  const [post, setPost] = useState(props.post);
  const [id, setId] = useState(props.id);
  console.log("reached thread method");
  const [isDeleted, setIsDeleted] = useState(false);
  function deleteHandler() {
    setIsDeleted(true);
    props.onDeleteThread(id);
  }
  return (
    <div className="thread">
      {post}
      <button className="button" onClick={deleteHandler}>
        delete
      </button>
    </div>
  );
}
export default Thread;

CodePudding user response:

In the Main:

if (props.newPost) {
    onPostSubmitHandler(props.newPost);
}

after you newPost is set and passed as a prop to this component, this if block will always return true.

Due to which, the method onPostSubmitHandler will be executed whenever the Main component re-renders (i.e., when the props/state of the component changes, in this case newPost).

Moreover, you're scheduling a state change in the first line of this method onPostSubmitHandler which is causing you infinite re-rendering.

You should use useEffect hook instead of the if check, with your props.newPost variable in the list of dependencies.

You can use object destructuring, to get the newPost variable and then pass it in the dependency array like this:

const { newPost } = props;

useEffect(() => {
    onPostSubmitHandler(newPost);
}, [newPost]);
  • Related