vdmrgv/react-easy-infinite-scroll-hook

Reverse mode - scroll position jump after first data load

Closed this issue · 3 comments

Describe the bug
Scroll position jumps after first setData execution.

Animation

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

Hi @vdmrgv, thanks for explanation, i will stick to the hackfix so far :)