Prevent parent from rerendering while setting state from children in React

Viewed 50

I have this simple chat app, containing a parent component Chat, inside it I loop through messages and create a ChatLine component for each message. I also have a small menu in the parent component component because I don't need to create a menu for each child, and I have a setOpenMenu to toggle it, I'm passing the line, openMenu & setOpenMenu as props to the child ChatLine.

The entire code is also available in CodeSandbox & Netlify

Here is the code : (Chat.js)

import { useState } from "react";
import ChatLine from "./ChatLine";

const Chat = () => {
  const [openMenu, setOpenMenu] = useState(null);
  const [chatLines, setChatLines] = useState([
    { id: 1, message: "Hello" },
    { id: 2, message: "Hi" },
    { id: 3, message: "Bye" },
  ]);

  return (
    <div>
      {openMenu && (
        <div
          style={{
            width: "100px",
            height: "100px",
            backgroundColor: "#000",
            borderRadius: "5px",
            position: "absolute",
            left: "200px",
          }}
        >
          <div>EDIT</div>
          <div>DELETE</div>
          <div>MORE...</div>
        </div>
      )}
      <p>MESSAGES :</p>
      {chatLines.map((line) => (
        <ChatLine
          key={line.id}
          line={line}
          setOpenMenu={setOpenMenu}
          openMenu={openMenu}
        />
      ))}
    </div>
  );
};

export default Chat;

& ChatLine.js :

const ChatLine = ({ line, setOpenMenu, openMenu }) => {
  return (
    <div>
      <span>{line.id}: </span>
      <span>{line.message}</span>
      <button
        onClick={() => {
          setOpenMenu(!openMenu || openMenu !== line.id ? line.id : null);
        }}
      >
        Options
      </button>
    </div>
  );
};

export default ChatLine;

So the problem is that the parent component Chat.js rerenders everytime ChatLine toggles the menu when I click in the Options button.

Is there a way to prevent the parent from rerendering when changing the menu's state while keeping the menu separate from ChatLine so we don't loop through it too and create a menu for each child for no reason?

EDIT : Is there maybe a way to use useCallback & memo to only rerender the parent and the affected child, without rerendering all the other children?

1 Answers

You need to use useEffect here.

Like this:

const [chatLines, setChatLines] = useState([]);
useEffect(() => setChatLines([
    { id: 1, message: "Hello" },
    { id: 2, message: "Hi" },
    { id: 3, message: "Bye" },
]), []);

Doing so you will render chat lines only once, when the component is mounted. But if you want to reload message on some external state changes, then you'll need to listen for this state changes in useEffect(() => effect(), [externalState])

Related