Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Prevent child rerenders when parent state is changed?

This is a simplified version of my problem

https://codesandbox.io/s/withered-night-6jbgft

import React, { useState, useCallback } from "react";
import "./styles.css";

const Box = React.memo(({ c, i, handleClick }) => {
  return (
    <div
      className="box"
      style={{ background: c }}
      onClick={() => handleClick(i)}
    ></div>
  );
});

export default function App() {
  const COUNT_BOXES = 250;
  const [colour, setColour] = useState("#00ff00");
  const [boxes, setBoxes] = useState(Array(COUNT_BOXES).fill("#ff00ff"));

  const changeColour = useCallback(
    (i) => {
      setBoxes((prevBoxes) => {
        prevBoxes[i] = colour;
        return [...prevBoxes];
      });
    },
    [colour]
  );

  return (
    <div className="App">
      <input
        type="color"
        value={colour}
        onChange={(e) => setColour(e.target.value)}
      />
      Change colour
      <div className="wrapper">
        {boxes.map((c, i) => (
          <Box key={i} c={c} i={i} handleClick={changeColour} />
        ))}
      </div>
    </div>
  );
}

I have a series of components that can be interacted with (the boxes). When you click a box, it changes colour. The colour it gets changed to is another piece of state in the parent component. The current implementation means that when the colour state changes, then useCallback creates a new function for changeColour, which is passed to every box. So every box rerenders.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

How can I refactor this so that every box doesn’t need to rerender when the parent colour changes?

>Solution :

One possibility would be to store the parent’s changeColour function inside a ref, so that the reference is stable. It’s not the usual React way of doing things, but it works.

const { useState, useCallback, useEffect } = React;

const Box = React.memo(({ c, i, handleClickRef }) => {
  console.log('render');
  return (
    <div
      className="box"
      style={{ background: c }}
      onClick={() => handleClickRef.current(i)}
    ></div>
  );
});

function App() {
  console.log('app rendering');
  const COUNT_BOXES = 250;
  const [colour, setColour] = useState("#00ff00");
  const [boxes, setBoxes] = useState(Array(COUNT_BOXES).fill("#ff00ff"));
  const changeColourRef = React.useRef();
  const changeColour = useCallback(
    (i) => {
      setBoxes((prevBoxes) => {
        prevBoxes[i] = colour;
        return [...prevBoxes];
      });
    },
    [colour]
  );
  useEffect(() => {
    changeColourRef.current = changeColour;
  }, [colour]);

  return (
    <div className="App">
      <input
        type="color"
        value={colour}
        onChange={(e) => setColour(e.target.value)}
      />
      Change colour
      <div className="wrapper">
        {boxes.map((c, i) => (
          <Box key={i} c={c} i={i} handleClickRef={changeColourRef} />
        ))}
      </div>
    </div>
  );
}


ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
.App {
  font-family: sans-serif;
  text-align: center;
  padding: 30px;
}
.wrapper {
  display: flex;
  flex: 0 0 40px;
  flex-wrap: wrap;
}

.box {
  cursor: pointer;
  width: 20px;
  height: 20px;
  border: 1px solid black;
}
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>

It would be nicer if you could just let the children re-render – it usually won’t be a problem (and if it is a problem, usually it’s fixable by tweaking your code, while continuing to let the component re-render).

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading