0

I am using react-virtualized to prevent items in a long list being loaded when out of view port. However, I have a button at the top of the page that when clicked, scrollsTo an item that is rendered with react-virtualized.

It uses React.createRef() to link the button with the list item but because the rendered item doesn't exist in the dom, the ref is null (It works for the first few items that get rendered but not the rest of the items).

For example, if I click button 20, then I get the error Cannot read properties of null (reading 'getBoundingClientRect') because the ref of the button is null because it's counterpart ref doesn't exist

Is there a way to scrollTo items that have been rendered within react-virtualized?

I have created a code sandbox here to show case what I mean.

Otherwise, here is the code snippet:

export default function App() {
  const onClick = (item) => {
    window.scrollTo({
      top: item.ref.current.getBoundingClientRect().top,
      behavior: "smooth"
    });
  };

  const items = arr.map((el) => ({
    ref: createRef(),
    id: el.id
  }));

  return (
    <div className="App">
      {items.map((item) => (
        <button onClick={(e) => onClick(item)}>Scroll to {item.id}</button>
      ))}

      <WindowScroller>
        {({ height, scrollTop, registerChild }) => (
          <div ref={registerChild}>
            <AutoSizer disableHeight>
              {({ width }) => (
                <>
                  <List
                    autoHeight
                    width={width}
                    height={height}
                    rowHeight={100}
                    rowRenderer={({ index }) => {
                      return (
                        <div
                          ref={items[index].ref}
                          id={`item-${items[index].id}`}
                          key={items[index].id}
                        >
                          {items[index].id}
                        </div>
                      );
                    }}
                    scrollTop={scrollTop}
                    rowCount={items.length}
                    overscanRowCount={100}
                  />
                </>
              )}
            </AutoSizer>
          </div>
        )}
      </WindowScroller>
    </div>
  );
}
Stretch0
  • 8,362
  • 13
  • 71
  • 133

3 Answers3

0

Add a reference to your <List>, and use that to scroll to the item's index.

const listRef = React.createRef<List>();

function scrollToItem(item) {
  const index = calculateIndex(item) // figure out what index the item should have
  listRef.current.scrollToItem(index) // second parameter here could be 'auto', 'smart', 'center', 'end', 'start'
}

return (
  /* ... */
  <List
    ref={listRef}
    /* ... */
  />
  /* ... */
)
HaveSpacesuit
  • 3,572
  • 6
  • 40
  • 59
  • What would `calculateIndex(item) ` return? Another ref? – Stretch0 Oct 07 '21 at 16:30
  • No, it should be a number. You have your items array being rendered in the List, and I assume you have a way to figure out what index the item you want is at. – HaveSpacesuit Oct 07 '21 at 16:58
  • 1
    It appears scrollToItem is not a function as I am getting the error `listRef.current.scrollToItem is not a function`. Are you sure a ref created from createRef has that method or should it come from somewhere else? – Stretch0 Oct 07 '21 at 18:04
  • Is it perhaps a react-native method you're thinking of? The List component I am using in my example is a Web component that comes from the react-virtualized library I've linked in my original question – Stretch0 Oct 07 '21 at 18:07
  • Maybe it's a bit different. I use react-virtualized in my project and have done the thing you want to do. It looks like my list is defined in the 'react-window' package. And in fact, it is a FixedSizeList that I've aliased as List, so I was a little mixed up. But look into the react-window List and see if that can help you? – HaveSpacesuit Oct 07 '21 at 19:05
0

@HaveSpacesuit almost had it. The method is scrollToRow, not scrollToItem. I've copied their original code snippet with the correct method substituted in.

const listRef = React.createRef<List>();

function scrollToItem(item) {
  const index = calculateIndex(item) // figure out what index the item should have
  listRef.current.scrollToRow(index) // second parameter here could be 'auto', 'smart', 'center', 'end', 'start'
}

return (
  /* ... */
  <List
    ref={listRef}
    /* ... */
  />
  /* ... */
)
bry
  • 1
  • 2
-3

Let’s use the List component to render the list in a virtualized way:

const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
//...
<div className="list">
<List
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length} />
</div>

Notice two things.

First, the List component requires you to specify the width and height of the list. It also needs the height of the rows so it can calculate which rows are going to be visible.

The rowHeight property takes either a fixed row height or a function that returns the height of a row given its index.

Second, the component needs the number of rows (the list length) and a function to render each row. It doesn’t take the list directly.

  • Maybe I'm missing something but aren't I already providing the width, height and list length in my example? – Stretch0 Oct 07 '21 at 17:59