1

I have a text log component that I want to scroll to the bottom whenever it receives a new log entry, so I have it set the parent's scrollTop to it's scrollHeight in useEffect(). However, this has no effect:

const Log = ({ entries }: LogProps) => {
    const logRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
        if (logRef && logRef.current) {
            let parent = logRef.current.parentNode as HTMLDivElement
            parent.scrollTop = parent.scrollHeight;
        }
    })
    return (
        <Module className="log" title="Text Log">
            <div ref={logRef} className="log__entries">
                ...
            </div>
        </Module>

    )
}

This code on the other hand, works:

...
useEffect(() => {
    if (logRef && logRef.current) {
        let parent = logRef.current.parentNode as HTMLDivElement
        setTimeout(() => {
            parent.scrollTop = parent.scrollHeight;
        }, 100)
    }     
})
...

On my machine, I can set the timeout as low as 39, and it'll work consistently. Lower numbers have sporadic success. Presumably that number will vary by some performance metric, but I have no idea.

Console logging shows that the ref exists, and it does have height enough to scroll by the time useEffect() triggers. Logging before and after parent.scrollTop = parent.scrollHeight; reveals that scrollTop doesn't change.

Am I misunderstanding how useEffect() works, is it because I'm setting the parent's scrollTop, or is there something else I'm missing?

1 Answers1

0

You can achieve it by making a few changes:

useEffect(() => {
  logRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' })
}, [entries])

You can see list of browsers supporting scrollIntoView feature at Mozilla Docs. If you want more browsers to support then you can install this tiny npm package called smoothscroll-polyfill.

Muhammad Ali
  • 2,538
  • 2
  • 16
  • 21
  • That solves my needing to reference the parent, but that call still isn't working unless it's within a timeout function for whatever reason. – RoboticWater Jan 27 '20 at 18:38
  • Is your `enteries` coming from some API call? If yes, is your state updating properly? Can you share a reproducible example? As it's working fine for me. – Muhammad Ali Jan 27 '20 at 19:32