javascriptreactjsreact-window

react-window stop unnecessary rendering of child FixedSizeList rows when scrolling parent FixedSIzeList


In my react project I am using react-window package to render nested lists. Each parent FixedSizeList row renders a component which uses another FixedSizeList. Parent List doesn't have more than 14 rows at the moment. But the child List may contain upto 2000 rows. Now my problem is, when I try to scroll through the parent List, all the child list items in the viewport seem to re rendering. This is a little bit problematic for me because in my child list item I am using d3js to draw bar chart with transition effect. So these unnecessary re rendering is giving a overall weird UI. Can anyone help me how can I stop these unnecessary renders.

Here is codesandbox link to a very simple example of my problem. Please open the console log. After initial load the topmost log should be like this: initial console log.

Then if you clear the console and scroll the parent list, you will see log like this: console log after parent scrolling. Here you can see that the child list items of child list 0 is re rendering which is not needed for me.

Can anyone give me a solution that can stop these re rendering?

*P.S. I am not using memo since every row is updating the dom on its own.

Edit

I think this problem would solve if the parent list would stop propagating scroll event to child. I tried to add event.stopPropagation() and event.stopImmediatePropagation() in the parent list row but the output was the same as earlier.


Solution

  • We can use memo to get rid of components being re-rendered unnecessarily for same set of props. And use useCallback to prevent re-creation of a function and thus secure child components being re-rendered. Applying those, we can get this solution:

    import "./styles.css";
    import { FixedSizeList as List } from "react-window";
    import { memo, useCallback } from "react";
    
    const Row = memo(({ index: parentIndex, style: parentStyle }) => {
      console.log("rendering child list", parentIndex);
    
      const InnerRow = useCallback(({ index, style }) => {
        console.log("rendering child list item", index, "of parent ", parentIndex);
        return <div style={style}>Child Row {index}</div>;
      }, []);
    
      return (
        <div style={parentStyle}>
          <List height={200} itemCount={1000} itemSize={35} width={270}>
            {InnerRow}
          </List>
        </div>
      );
    });
    
    const Example = () => {
      console.log("rendering parent list");
      return (
        <List height={400} itemCount={16} itemSize={300} width={300}>
          {Row}
        </List>
      );
    };
    
    export default function App() {
      return (
        <div className="App">
          <Example />
        </div>
      );
    }