Why does scrollTop not take the value being set

I am running into an issue when setting scrollTop where, when set with a value, it reverts back to it’s old value. The result is a momentary flash as the scrollbar moves and then goes back to it’s original position. I am seeing this issue cross browser and cross operating systems.

There are some additional particulars to this issue which makes it even more weird.

  1. It only happens while you are manipulating the scrollbar. If you use your mouse wheel there is no problem.
  2. In the example I am providing, if you disable, for the outer div, either the overflow CSS attribute or both border radius attributes this problem goes away. To add to my misery the example I am providing is a HIGHLY boiled down version of the original program. In this program the outer div is beyond my control, and(hold on to your seat), exists OUTSIDE of the iframe my code is running in. THAT’S RIGHT! Some how the CSS is affecting stuff running inside an iframe. I checked and I cannot see the iframe change size or have it’s own CSS or attributes being modified in any way.

I have a fiddle which demonstrates the issue, however, to see how removing the CSS also removes the issue, it’s best ran on it’s own from your IDE. I think jsFiddle has their own CSS somewhere outside the iframe it’s running in causing the problem.

https://jsfiddle.net/CodeMedic42/oxut78b2/3/

I provided a debugger statement to stop your debugger where the issue is occurring. To see the issue, scroll down, using your mouse and dragging the scroll bar, to 1985:11 and when you get just past that, new items are added to the top of the list which pushes the scroll bar down. What I am doing is then resetting the scrollbar back to the spot where is was moved from.

If you haven’t guessed I am making an infinite list component. I ran into this issue while running it on Storybook.

Also if you are asking why I am using “overflow-anchor: none;” I will angrily refer you to Apple to ask them why Safari on Mac and iOS is the only modern browser that does not support it. If they did I would not be here right now.

I need to provide code for the jsFiddle link or StackOverflow won’t accept it. It’s just a copy from the fiddle.

function chooseColor(index) {
    const v = index % 10;

    if (v === 0) {
        return 'purple';
    }

    if (v === 1) {
        return 'orange';
    }

    if (v === 2) {
        return 'green';
    }

    if (v === 3) {
        return 'yellow';
    }

    if (v === 4) {
        return 'magenta';
    }

    if (v === 5) {
        return 'red';
    }

    if (v === 6) {
        return 'black';
    }

    if (v === 7) {
        return 'brown';
    }

    if (v === 8) {
        return 'pink';
    }

    return 'cyan';
}

function List() {
    const listRef = React.useRef();
    const itemsRef = React.useRef();
    const itemsRemoved = React.useRef(false);
    const shiftInfoRef = React.useRef(null);

    const [itemsMeta, setItemsMeta] = React.useState({ from: { year: 1974, month: 3 }, to: { year: 1991, month: 8 }});

    const items = React.useMemo(() => {
        const builder = [];
        let startingMonth = itemsMeta.from.month;
        let endingMonth = 12;

        for (let yearCounter = itemsMeta.from.year; yearCounter <= itemsMeta.to.year; yearCounter += 1) {
            if (yearCounter === itemsMeta.to.year) {
                endingMonth = itemsMeta.to.month;
            }

            for (let monthCounter = startingMonth; monthCounter <= endingMonth; monthCounter += 1) {
                startingMonth = 1;

                const color = chooseColor(yearCounter);
              
                const text = yearCounter + ':' + monthCounter;

                builder.push(<div key={yearCounter * 12 + monthCounter}style={{color: 'white', fontSize: 48, margin: 20, backgroundColor: color}}>{text}</div>);
            }
        }

        return builder;
    }, [ itemsMeta ]);

    const handleScroll = React.useCallback(() => {
        const scrollOn = 140;

        const targetChild = itemsRef.current.childNodes[scrollOn];
        const item = itemsRef.current.childNodes[scrollOn + 1];

        const childRect = targetChild.getBoundingClientRect();
        const listRect = listRef.current.getBoundingClientRect();
        const itemRect = item.getBoundingClientRect();

        if (!itemsRemoved.current && childRect.bottom < listRect.top) {
            console.log('Add items');
            itemsRemoved.current = true;

            setItemsMeta({ from: { year: 1979, month: 4 }, to: { year: 1991, month: 8 }});

            shiftInfoRef.current = {
                previousPosition: itemRect.top,
                item,
            };
        }
    });

    React.useLayoutEffect(() => {
        const targetChild = itemsRef.current.childNodes[70];

          targetChild.scrollIntoView({
            block: 'start',
            inline: 'start',
        });
    }, []);

    React.useLayoutEffect(() => {
        if (shiftInfoRef.current == null || itemsRef.current == null) {
            return;
        }

        const { previousPosition, item } = shiftInfoRef.current;

        const itemRect = item.getBoundingClientRect();

        const scrollTop = listRef.current.scrollTop - (previousPosition - itemRect.top);

        shiftInfoRef.current = null;

        debugger;
        // This fails when scrolling using the scrollbar, but works when using the mouse wheel. #rage
        listRef.current.scrollTop = scrollTop;
    });

    return (
        <div style={{
            position: 'absolute',
            width: '100%',
            height: '100%',
            borderTopLeftRadius: 5,
            borderBottomLeftRadius: 5,
            overflow: 'hidden',
        }}>
            <div
                ref={listRef}
                className="list"
                onScroll={handleScroll}
            >
                <div style={{ fontSize: '128px', margin: 20 }}>
                    Top Header
                </div>
                <div ref={itemsRef}>
                    {items}
                </div>
            </div>
        </div>
    );
}
:root {
    scroll-behavior: smooth;
}

* {
    box-sizing: border-box;
}

html,
body,
#app {
  padding: 0;
  margin: 0;
  background-color: white;
  height: 100%;
}

.list {
  height: 100%;
  overflow: auto;
  box-sizing: border-box;
  overflow-anchor: none;
}