React-window: Getting Invalid warnings when creating own ref

Created on 6 Sep 2019  路  6Comments  路  Source: bvaughn/react-window

What I Expect
When I am mid scroll, and the user changes the input, the scroll index should jump back to the top of the list once the new data loads.

What is happening
I was able to reset the scroll back to the top, but now I am getting warnings that the list ref and the onItemsRendered is invalid. Even though they are warnings they are hindering the list from populating. I have tested this by taking out the scrollToTop logic and replacing the List ref with the ref provided by InfiniteLoader.

Am I implementing the new ref incorrectly?

image
image

The Code

import React from 'react';
import PropTypes from 'prop-types';
import Downshift from 'downshift';
import styled from 'styled-components';
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import MenuItem from '@material-ui/core/MenuItem';
import { withStyles } from '@material-ui/core/styles';

import BaseComponent from '../BaseComponent';

export class SearchFieldInfiniteScrollComponent extends BaseComponent {
  listRef = React.createRef();
  scrollToTop() { 
    this.listRef.current.scrollToItem({ align: 'start' });
  } 

  render() {
    const {
      dataArray,
      dropdownWidth,
      handleInput,
      handleSelection,
      isFirstListRender,
      isRowLoaded,
      itemContent,
      listHeight,
      loadedRowCount,
      loadingIndicator,
      loadMoreRows,
      placeholder,
      rowHeight,
      totalCount,
      threshold,
    } = this.props;

    return (
      <SearchContainer>
        <Downshift
          onChange={handleSelection}
          stateReducer={stateReducer}
          itemToString={item => (item ? item.value : '')}
        >
          {({ getInputProps, getMenuProps, isOpen, highlightedIndex, getItemProps }) => {
            return (
              <div>
                <Input
                  {...getInputProps({
                    onChange: e => {
                      if (!e.target.value.length) {
                        this.scrollToTop();
                      }

                      return handleInput(e);
                    },
                  })}
                  placeholder={placeholder}
                />
                <UnorderedList {...getMenuProps()}>
                  {isOpen && dataArray && (
                    <InfiniteLoader
                      isItemLoaded={isRowLoaded}
                      loadMoreItems={loadMoreRows}
                      itemCount={loadedRowCount}
                      threshold={threshold}
                    >
                      {({ onItemsRendered }) => (
                        <List
                          ref={this.listRef}
                          height={listHeight}
                          onItemsRendered={onItemsRendered}
                          itemCount={loadedRowCount}
                          itemSize={rowHeight}
                          width={dropdownWidth}
                        >
                          {({ index, style }) => {
                            let content = (
                              <Loading>
                                {(isFirstListRender || index < totalCount) && loadingIndicator}
                              </Loading>
                            );

                            if (dataArray[index]) {
                              content = (
                                <StyledMenuItem
                                  selected={highlightedIndex === index}
                                  component='div'
                                  {...getItemProps({
                                    key: itemContent,
                                    index,
                                    item: dataArray[index],
                                  })}
                                >
                                  {itemContent(index)}
                                </StyledMenuItem>
                              );
                            }

                            return (
                              <RowContainer key={`${index}-${Math.random()}`} style={style}>
                                {content}
                              </RowContainer>
                            );
                          }}
                        </List>
                      )}
                    </InfiniteLoader>
                  )}
                </UnorderedList>
              </div>
            );
          }}
        </Downshift>
      </SearchContainer>
    );
  }
}

export default withStyles(styles, { withTheme: true })(SearchFieldInfiniteScrollComponent);
馃挰 question

Most helpful comment

ref is not what you think it is, as @nihgwu mentioned and as the docs I linked to above indicate :) It's a ref (function) you're meant to pass on and attach to the inner list so that infinite loader gets a ref to that list.

{({ onItemsRendered, ref }) => (
  <List
    ref={list => {
      ref(list); // Give InfiniteLoader a reference to the list
      this.listRef.current = ref; // Set your own ref to it as well.
    }}
  />
)}

I'm going to close this issue, since I'm pretty sure it's been been answered now. If you are still running into any additional problems, it would probably be best provide a complete runnable repro in Code Sandbox. :)

All 6 comments

Please read the docs. The ref property needs to be attached to the inner list, as shown in the docs.
Screen Shot 2019-09-05 at 5 10 36 PM

This is what the warning message is talking about.

Fix this first, then if things are still broken- we can talk more.

This is what I based the idea off of..
https://codesandbox.io/s/github/bvaughn/react-window/tree/master/website/sandboxes/scrolling-to-a-list-item

When I use the ref provided from the infiniteLoader I get the following error
image

For my component to work I need to call to the ref and reset the scroll when the input is emptied. By replacing listRef with the ref of the inner list I continuously receive that error.

Here is the code that I changed

...
export class SearchFieldInfiniteScrollComponent extends BaseComponent {
  listRef;
  scrollToTop() {
    this.listRef.current.scrollToItem({ align: 'start' });
  }

  render() { 
...

<InfiniteLoader
                      isItemLoaded={isRowLoaded}
                      loadMoreItems={loadMoreRows}
                      itemCount={loadedRowCount}
                      threshold={threshold}
                    >
                      {({ onItemsRendered, ref }) => {
                        this.listRef = ref;
                        return (
                          <List
                            ref={ref}
                            height={listHeight}
                            onItemsRendered={onItemsRendered}
                            itemCount={loadedRowCount}
                            itemSize={rowHeight}
                            width={dropdownWidth}
                          >

I should mention that when I try to access the scrollToItem method with the inner ref within the List component I still get the above error

you should understand which ref is for which component, try <List ref={listRef => { this.listRef = listRef; ref(listRef); }} />

ref is not what you think it is, as @nihgwu mentioned and as the docs I linked to above indicate :) It's a ref (function) you're meant to pass on and attach to the inner list so that infinite loader gets a ref to that list.

{({ onItemsRendered, ref }) => (
  <List
    ref={list => {
      ref(list); // Give InfiniteLoader a reference to the list
      this.listRef.current = ref; // Set your own ref to it as well.
    }}
  />
)}

I'm going to close this issue, since I'm pretty sure it's been been answered now. If you are still running into any additional problems, it would probably be best provide a complete runnable repro in Code Sandbox. :)

ref={list => {
  ref(list); // Give InfiniteLoader a reference to the list
  this.listRef.current = ref; // Set your own ref to it as well.
}}

My case:
Instead of the this.listRef.current = ref; i'm wrote this.listRef.current = list;

_InfntLdrHOC.tsx_

import InfiniteLoader from 'react-window-infinite-loader';
...
<InfiniteLoader
  isItemLoaded={isItemLoaded}
  loadMoreItems={onScroll}
  itemCount={listCount}
>
  {({ onItemsRendered, ref }) => {
    return (
      <ListOfMessages
        config={{
          messages,
          height,
          width,
          ref,
          onItemsRendered,
          isFetchingNewScenario,
        }}
      />
    );
  }}
</InfiniteLoader>

_ListOfMessages.tsx_

import React from 'react';
import { VariableSizeList as List } from 'react-window';

import Message from '../Message';

class ListOfMessagesClass extends React.Component<any> {
  private listRef = React.createRef<any>();


  componentDidUpdate(prevProps: any) {
    const {
      isFetchingNewScenario: isFetchingNewScenarioPrev,
    } = prevProps.config;
    // eslint-disable-next-line react/destructuring-assignment
    const { isFetchingNewScenario } = this.props.config;

    if (isFetchingNewScenarioPrev && !isFetchingNewScenario) {
      this.scrollToNewMessage();
    }
  }


  scrollToNewMessage = () => {
    this.listRef.current!.scrollToItem(0);
  };

  render() {
    const { config } = this.props;
    const {
      messages = [],
      listCount,
      height,
      width,
      ref,
      onItemsRendered,
    } = config;

    const listLength = messages.length;

    return (
      <>
        <List
          height={height}
          itemCount={listLength}
          itemSize={(index: any) => messages[index].messageRenderHeight}
          width={width}
          ref={list => {
            ref(list); // Give InfiniteLoader a reference to the list
            // @ts-ignore
            this.listRef.current = list; // Set your own ref to it as well.
          }}
          onItemsRendered={onItemsRendered}
          itemData={messages}
          outerRef={this.outerRef}
        >
          {Message}
        </List>
      </>
    );
  }
}

export default ListOfMessages;

Was this page helpful?
0 / 5 - 0 ratings