React virtualized infiniteLoader scrolling to top on load using List - reactjs

I am currently using react-virtualized InfiniteLoader component to scroll over a List of elements that could be variable all the time.
This is my component now:
<VirtualizedInfiniteLoader isRowLoaded={isRowLoaded} loadMoreRows={loadMoreRows} rowCount={rowCount}>
{({ onRowsRendered, registerChild }) => (
<AutoSizer>
{({ width, height }) => (
<List
key={`${width}${height}${rowCount}`}
height={height - headerHeight}
onRowsRendered={onRowsRendered}
scrollToIndex={scrollToIndex}
scrollToAlignment={scrollToAlignment}
ref={registerChild}
rowCount={rowCount}
rowHeight={rowHeight}
rowRenderer={rowRenderer}
width={width}
onScroll={onListScroll}
/>
)}
</AutoSizer>
)}
</VirtualizedInfiniteLoader>
And i'm using this in a container:
renderInfiniteLoader = currentStatus => {
const { cache, headerHeight } = this.state;
const { scrollToIndex, params, allTasks, selectedFilterFields, searchFilters } = this.props;
const list = allTasks[currentStatus];
console.log(list.length, params);
return (
<InfiniteLoaderWrapper
diffHeight={marginBottomToAdd({ searchFilters, selectedFilterFields, customerID: params.customerID })}
ref={this.cardsContainer}
>
<InfiniteLoader
loadMoreRows={options => this.loadMoreRows(options, status)}
rowRenderer={this.renderTasks(list, currentStatus)}
isRowLoaded={this.isRowLoaded(list)}
scrollToIndex={scrollToIndex}
scrollToAlignment="start"
rowCount={list.length + 1}
headerHeight={150}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
onListScroll={this.onInfiniteLoaderScroll}
/>
</InfiniteLoaderWrapper>
);
};
The problem here is that everytime i scroll down and a loader appear if i'm scrolling too fast, page scroll to first element on top.
The prop scrollToIndex seems to be 0 everytime so probably that's the problem.
If i remove that prop, scrolling works correctly, but comes up another issue:
I can click on list's element to go to a detail page, and if i come back i expect to be in the correct position of the list, but i'm on top (again, because scrollToIndex is 0).
What is the correct value to pass to scrollToIndex to be always in the right point?
I can see there was this bug in some version of react-virtualized but can't find any workaround.
Thanks in advance!

Related

react-virtulized list with ArrowKeyStepper not working

I am trying to get my react-virtualized List component to work together with ArrowKeyStepper without success. I must be missing something but could not find any examples of this implementation. There are no errors it just does not function as expected.
So far my code looks like this:
import { List as VirtualizedList, AutoSizer, ArrowKeyStepper } from "react-virtualized";
...skip a lot of other stuff
<div data-testid="popover-items">
<ArrowKeyStepper columnCount={1} rowCount={numFilteredOptions} mode="cells">
{({ onSectionRendered, scrollToRow, scrollToColumn }) => (
<AutoSizer disableHeight={!fullScreen}>
{({ width, height }) => (
<VirtualizedList
rowHeight={rowHeight}
height={fullScreen ? height - 175 : VIRTUAL_LIST_HEIGHT}
onRowsRendered={onSectionRendered}
columnCount={1}
rowCount={numFilteredOptions}
rowRenderer={renderRow}
noRowsRenderer={() => null}
scrollToRow={scrollToRow}
scrollToColumn={scrollToColumn}
width={width}
/>
)}
</AutoSizer>
)}
</ArrowKeyStepper>
</div>
Expected Behavior: arrow up/down would move the selection of an item in the list.
Actual Behavior: nothing happens.

React scrollIntoView() not working with ref

I have a react functional component defined as
export const NavMenuHorizontal: React.FunctionComponent<NavMenuProps> = (
props
) => {
const selectedItemRef = React.createRef<HTMLDivElement>();
React.useEffect(() => {
selectedItemRef.current?.scrollIntoView({
behavior: "smooth",
inline: "center",
})
});
return (
<List>
{map(...) => (
<div ref={isActive ? selectedItemRef : undefined}>
some text
</div>
)}
</List>
);
};
//isActive is true for only one item in the map
I am trying to make the selected list item scroll into view using a ref but unable to get the expected result. However, if I try to add debug breakpoint at the hook, it's working as expected and scrolling the element in viewport.
I also tried wrapping my scrollIntoView inside a setTimeout() which works but it scrolls it more than once and leads to a jarring effect.

Append components to a fixed list using react-window?

I have a fixed list and grid using react-window, infinite loader, and auto sizer. It's working fine but I'm looking to append a component before the list/grid starts (for example, a search box and a button). What's the correct approach to accomplish this? I want this component to scroll with the fixed list and not scroll separately. I've tired just rendering the component before but then it's not in the fixed list container and doesn't scroll with it.
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS NOT INSIDE THE FIXED CONTAINER SO IT SCROllS SEPARATELY */}
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={index => index < stateData.data.length}
itemCount={stateData.data.length + 1}
loadMoreItems={loadMoreProducts}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
onItemsRendered={onItemsRendered}
ref={ref}
height={height}
itemCount={stateData.data.length + 1}
itemSize={350}
width={width}
>
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS LOOPING LISTITEM */}
{ListItem}
</FixedSizeList>
)}
</InfiniteLoader>
)}
</AutoSizer>
Edit: Pretty much I don't want the list (or grid) and the actual page to be two different scroll containers. I'd like the whole page to scroll together. I've come across this issue because some of my containers need to have an infinite list of items users can scroll through so the list needed to be virtualized to improve performance.
See a demo here. Really the fixed container should just be considered the whole page and the search box and everything else should scroll with the infinite list. Having two different scroll containers isn't too great for the ux.
If you want the whole page to scroll with your virtual list you will need a window scroller. This also involves playing around with some styling as well. To make your searchbox scroll with your list you will need a position of fixed. A spacer div and some styling helps the illusion.
I added a basic window scroller and some styling changes to your codepen here.
A few options:
Make a wrapper around The autosizer, that holds both your search box and the autosizer.
<>
<SearchBox />
<AutoSizer>
... your items
</AutoSizer>
</>
let the AutoSizer contain both the search and the list
<AutoSizer>
<SearchBox />
<List>
... your items
</List>
</AutoSizer>
Since your infinite scroll is using a render function you might need fragments
<AutoSizer>
<InfiniteScroll>
{({ onItemsRendered, ref }) => (
<>
<SearchBox />
<List>
... your items
</List>
<>
)}
</InfiniteScroll>
</AutoSizer>
Here I edited your example and made it work using this approach.
Please note that the page scrolls only because of the top and bottom bars; which is the behaviour in your example.
Let the infinite loader populate an array, and append an extra item to that. The item can have a certain type. Assuming the FixedSizeList is the List component from react-virtualized then you can use it's rowRenderer to render the item of type "search" differently than the other items. Something like:
function rowRenderer ({ key, type, value, ...rest }) {
if (type === 'search') {
return <Search key={key} placeholder={value} {...rest} />
}
return <div key={key} {...rest}>{value}</div>
}
// Render your list
ReactDOM.render(
<AutoSizer>
{({ height, width }) => (
<List
height={height}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
width={width}
/>
)}
</AutoSizer>,
document.getElementById('example')
);
Perhaps it also helps to look at the simple example from Vaughn.

How to know implement auto follow with react-virtualized

I am using the List and WindowScroller in react-virtualized to display logs. It seems that there is no good way to implement the auto-follow. It seems pretty easy to scroll to the bottom with scrollToIndex. But it is pretty hard to know whether we are at the bottom of the page. the document.body.clientHeight is much smaller than the scrollTop provided by WindowScroller. How could we implement this feature? Thanks.
you can use the prop "scrollToIndex" when his value is equal to your row count
render(): React.Element<AutoSizer> {
const { data, ...otherProps } = this.props;
return (
<AutoSizer onResize={this.onResizeHandler}>
{({ width, height }) => (
<List
{...otherProps}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowCount={data.length}
forceUpdateGrid={data}
rowRenderer={this.rowRenderer}
scrollToIndex={data.length - 1}
onScroll={this.onScrollHandler}
height={height}
width={width}
/>
)}
</AutoSizer>
);
}

scroll to bottom/row on react-virtualized with autosizer and cellmeasurer

I am building a chat app and using react-virtualized to manage display / infinite load (through a custom method, not the HOC) of chat messages. I am using Autosizer to fill the container div, and cellmeasurer to calculate row heights. Everything is working great except when I attempt to scroll down to the last/newest message at the bottom of the list, it takes me allllmost there. Typically the next to bottom row is visible and I need to scroll down just a bit more to see the actual bottom row.
Here are relevant snippets:
Render Method:
render() {
const hasNextPage = this.props.contact ? this.props.contact.messages.length < this.props.contact.totalMessageCount : false
// const totalMessageCount = this.props.contact ? this.props.contact.totalMessageCount : 0
const contactMessages = this.props.contact ? sortBy(this.props.contact.messages, "created_at") : []
const rowCount = contactMessages.length // hasNextPage ? contactMessages.length + 1 : contactMessages.length
// auto resizer copy-spiration
// https://github.com/bvaughn/tweets/blob/37d0139736346db16b9681d5b859a4e127964518/src/components/TweetList.js#L126-L132
const _rowRenderer = ({ index, key, parent, style }) => {
let content;
if (index === 0 && hasNextPage) {
content = 'Loading...'
} else {
content = <ChatMessage message={contactMessages[index]} />
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
<div
key={key}
style={style}
>
{content}
</div>
</CellMeasurer>
);
};
return (
<div style={{ height: '65vh' }}>
<AutoSizer>
{({ height, width }) => (
<List
deferredMeasurementCache={this._cache}
// eslint-disable-next-line no-return-assign
ref={ref => this.chatWindow = ref}
onRowsRendered={(renderData) => this.handleRowsRendered(renderData, hasNextPage, rowCount)}
rowRenderer={_rowRenderer}
height={height}
width={width}
rowHeight={this._cache.rowHeight}
rowCount={rowCount}
/>
)}
</AutoSizer>
</div>
)
}
And my call to scroll on componentDidUpdate:
componentDidUpdate() {
if (this.chatWindow && !this.state.initialized && this.props.contact) {
// this.chatWindow.recomputeRowHeights(this.props.contact.messages.length - 10)
this.chatWindow.scrollToRow(this.props.contact.messages.length || 0)
}
}
Any ideas how I can achieve that list little amount of scroll to make the bottom message visible?
I was having same problem in List solved it by using hack of setting an estimatedRowSize parameter to my average row size value.
<List
rowHeight={this.getRowHeight}
rowRenderer={this.rowRenderer}
onRowsRendered={this.onRowsRendered}
rowCount={totalRows}
// Row size It may vary in my case it is 44.5.
// Also it doesnt need to be exact.
//Approx value will work but estimated row size should not be less than actual row height.
estimatedRowSize={44.5}
overscanRowCount={10}
height={height}
width={width}
/>

Resources