Reverse mode - scroll position jump after first data load
Closed this issue · 3 comments
Describe the bug
Scroll position jumps after first setData
execution.
To Reproduce
minimal reproducible demo:
import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react'
import useInfiniteScroll from 'react-easy-infinite-scroll-hook'
import { FixedSizeList } from 'react-window'
export const loadMore = async (length = 50): Promise<string[]> => new Promise((res) => setTimeout(() => res(createItems(length)), 1000))
export const createItems = (length = 100): string[] => {
return Array.from({ length }).map((_e, i) => `item ${i}`)
}
export const createNext =
({ setLoading, setData, offset }: { setData: (v: SetStateAction<string[]>) => void; setLoading: (v: SetStateAction<boolean>) => void; offset: number }) =>
async () => {
try {
setLoading(true)
const rows = await loadMore(offset)
setData((prev) => [...rows, ...prev])
} finally {
setLoading(false)
}
}
const ReversedVerticalList = () => {
const [data, setData] = useState<string[]>(() => createItems())
const listRef = useRef<FixedSizeList>()
const ref = useInfiniteScroll<HTMLDivElement>({
next: createNext({ setData, setLoading: () => ({}), offset: 50 }),
rowCount: data.length,
hasMore: {
up: true,
down: false,
},
scrollThreshold: '50px',
})
const setRVRef = useCallback((RVnode) => {
if (RVnode) {
ref.current = RVnode._outerRef
listRef.current = RVnode
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
listRef.current?.scrollTo(Number.MAX_SAFE_INTEGER)
}, [])
return (
<>
<FixedSizeList ref={setRVRef} width={500} height={500} itemSize={20} itemCount={data.length}>
{({ index, style }) => {
const item = data[index]
return <div style={style}>{item}</div>
}}
</FixedSizeList>
</>
)
}
export default ReversedVerticalList
Possible hackfix
problem can be hackfixed by skipping first setData call:
const ReversedVerticalList = () => {
const [data, _setData] = useState<string[]>(() => createItems())
const listRef = useRef<FixedSizeList>()
const [initialLoad, setInitialLoad] = useState(true)
const setData = (v) => {
!initialLoad && _setData(v)
}
const ref = useInfiniteScroll<HTMLDivElement>({
next: createNext({ setData, setLoading: () => ({}), offset: 50 }),
rowCount: data.length,
hasMore: {
up: true,
down: false,
},
scrollThreshold: '50px',
})
const setRVRef = useCallback((RVnode) => {
if (RVnode) {
ref.current = RVnode._outerRef
listRef.current = RVnode
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
listRef.current?.scrollTo(Number.MAX_SAFE_INTEGER)
setInitialLoad(false)
}, [])
return (
<>
<FixedSizeList ref={setRVRef} width={500} height={500} itemSize={20} itemCount={data.length}>
{({ index, style }) => {
const item = data[index]
return <div style={style}>{item}</div>
}}
</FixedSizeList>
</>
)
}
@vdmrgv could you think of some nicer solution 😊? Thanks.
Hi @ejdzipi
I have looked this problem and it seems that it should not be solved inside the react-easy-infinite-scroll-hook
. It looks like a FixedSizeList
scrolls the list down after mounting only if it has the original data in it, but at the time when useInfiniteScroll
loaded it for the first time, the list is still at the top, and this is the reason why scroll loads it twice and scrolls the list to a new position.
The problem is related to the feature of work react-window
, so I think this should not be directly implemented in react-easy-infinite-scroll-hook