0

Problem

I'm trying to put startIndex in state from within onRowsRendered().

This works fine, until CellMeasurer is put into the mix.

When scrolling down and then up, the following error occurs:

Uncaught Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

What causes this problem and what solves it?

Demo

Code

import React from "react";
import ReactDOM from "react-dom";
import faker from "faker";
import { List, CellMeasurer, CellMeasurerCache } from "react-virtualized";

import "./styles.css";

faker.seed(1234);

const rows = [...Array(1000)].map(() =>
  faker.lorem.sentence(faker.random.number({ min: 5, max: 10 }))
);

const App = () => {
  const [currentIndex, setCurrentIndex] = React.useState(0);

  const rowRenderer = ({ key, index, style, parent }) => {
    return (
      <div style={style}>
        <div style={{ borderBottom: "1px solid #eee", padding: ".5em 0" }}>
          {rows[index]}
        </div>
      </div>
    );
  };

  return (
    <>
      <h1>{currentIndex}</h1>
      <p>
        <em>When scrolling down and then up, an error occurs. Why?</em>
      </p>
      <List
        height={400}
        width={600}
        rowCount={rows.length}
        rowHeight={35}
        rowRenderer={rowRenderer}
        style={{ outline: "none" }}
        onRowsRendered={({ startIndex }) => {
          setCurrentIndex(startIndex);
        }}
      />
    </>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Willem-Aart
  • 2,200
  • 2
  • 19
  • 27

1 Answers1

1

You need to move rowRenderer and cellMeasurer function outside of your Functional Component. Because It will be recreated every time your functional component is rendered.

Functional Component: https://codesandbox.io/s/nnp9z3o9wj?fontsize=14

Or you can use Class Component:

import React from "react";
import ReactDOM from "react-dom";
import faker from "faker";
import { List, CellMeasurer, CellMeasurerCache } from "react-virtualized";

import "./styles.css";

faker.seed(1234);

const rows = [...Array(1000)].map(() =>
  faker.lorem.sentence(faker.random.number({ min: 5, max: 10 }))
);

class VirtualList extends React.Component {


 rowRenderer = ({ key, index, style, parent }) => {
    return (
      <div style={style}>
        <div style={{ borderBottom: "1px solid #eee", padding: ".5em 0" }}>
          {rows[index]}
        </div>
      </div>
    );
  };

  render() {
     return (
      <List
        height={400}
        width={600}
        rowCount={rows.length}
        rowHeight={35}
        rowRenderer={this.rowRenderer}
        style={{ outline: "none" }}
        onRowsRendered={this.props.setCurrentIndex}
      />
     )
   }
}

const App = () => {
  const [currentIndex, setCurrentIndex] = React.useState(0);


  return (
    <>
      <h1>{currentIndex}</h1>
      <p>
        <em>When scrolling down and then up, an error occurs. Why?</em>
      </p>
      <VirtualList setCurrentIndex={setCurrentIndex} />
    </>
  );
};
AlexZvl
  • 2,092
  • 4
  • 18
  • 32
  • Tried something [similar](https://codesandbox.io/s/n58pr6vzn0). Doesn't seem to make a difference. – Willem-Aart Mar 31 '19 at 15:20
  • This is fascinating. [Tried implementing it using a `PureComponent`](https://codesandbox.io/s/98rkrnmomp), as you suggested, and it seems to be working! Can you explain why this works? Works with [local state in a PureComponent](https://codesandbox.io/s/8kkr61pj2l), too. – Willem-Aart Mar 31 '19 at 15:24
  • Even works [with a regular class component](https://codesandbox.io/s/jzx7yqk4m9)... – Willem-Aart Mar 31 '19 at 15:30
  • @Willem-Aart to tell you the truth, I am not sure why :D Could it be because `rowRenderer` function is recreated every time in functional component ? – AlexZvl Mar 31 '19 at 15:33
  • @Willem-Aart try to put `rowRenderer` outside your functional component – AlexZvl Mar 31 '19 at 15:34
  • Great suggestion! Putting `cellMeasurerCache` outside of the function fixes the problem! It seems that `rowRenderer` can stay inside the function. Thank you so much for helping figuring this out! – Willem-Aart Mar 31 '19 at 15:41
  • 1
    @Willem-Aart you are welcome :) Could you mark it as a correct answer? – AlexZvl Mar 31 '19 at 15:42