clauderic / react-tiny-virtual-list

A tiny but mighty 3kb list virtualization library, with zero dependencies 💪 Supports variable heights/widths, sticky items, scrolling to index, and more!
https://clauderic.github.io/react-tiny-virtual-list/
MIT License
2.46k stars 165 forks source link

setState in onItemsRendered #57

Open maxsalven opened 6 years ago

maxsalven commented 6 years ago

I need to keep track of which items are visible outside of the VirtualList component. However, if I call setState in onItemsRendered, then React will correctly complain:

Warning: Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.

Is there are correct pattern for this? Ideally VirtualList would have a renderProp style prop that could look like ({ RenderedListNode, startIndex, endIndex}) => Node.

This is the code, it works in this simple example but causes the React warning and issues in more complicated code:

// @flow

import React from "react";
import VirtualList from "react-tiny-virtual-list";

const data = Array(1000)
  .fill(0)
  .map((item, index) => index);

type Props = {};

type State = {
  currentlyVisibleIndex: number,
};

class Test extends React.Component<Props, State> {
  state = { currentlyVisibleIndex: 0 };

  onItemsRendered = ({ startIndex, stopIndex }) => {
    const { currentlyVisibleIndex } = this.state;
    if (currentlyVisibleIndex !== startIndex) {
      this.setState({ currentlyVisibleIndex: startIndex });
    }
  };

  render() {
    const { currentlyVisibleIndex } = this.state;

    return (
      <div>
        <div>Currently Visible: {currentlyVisibleIndex}</div>
        <VirtualList
          width="100%"
          height={600}
          itemCount={data.length}
          itemSize={50}
          overscanCount={0}
          renderItem={({ index, style }) => (
            <div key={index} style={style}>
              {data[index]}, Row: #{index}
            </div>
          )}
          onItemsRendered={this.onItemsRendered}
        />
      </div>
    );
  }
}

export default Test;
charleskoehl commented 5 years ago

I just setState within a setTimeout of 1ms to solve this.