Open karthik2265 opened 2 years ago
Note that lastmessageRef
as a dependency won't do anything and can be removed. The effect won't fire if lastmessageRef.current
changes.
Could you include a video of the behavior in Firefox and Chrome and explain with words what's "weird" about this behavior? Also please include the exact version of those browsers and the operating system you're using.
Bonus: Could you convert the repro into a codesandbox.io? Makes it easier to debug the issue.
I'm having the same problem. scrollIntoView api doesn't work inside useEffect, while it fires by onClick. Worth mentioning that it works in Firefox. Here is the code:
import React, { useEffect, useRef, useState } from 'react'
import FirstPage from './components/FirstPage'
import SecondPage from './components/SecondPage'
import ThirdPage from './components/ThirdPage'
import './App.css'
const App = () => {
const [atPage, setAtPage] = useState<number>(0)
const refs = useRef<(null | HTMLDivElement)[]>([])
const pages = [<FirstPage />, <SecondPage />, <ThirdPage />]
const handleWheel = (event: any) => {
console.log(event.deltaY > 0 ? 'down' : 'up')
setAtPage(atPage => {
let atPageCopied = JSON.parse(JSON.stringify(atPage))
return event.deltaY < 0 ? (atPage > 0 ? atPageCopied - 1 : atPage) : (atPage < pages.length - 1 ? atPageCopied + 1 : atPage)
})
}
const scrollItThere = (atPage: number) => refs.current[atPage]?.scrollIntoView({ block: 'start', behavior: 'smooth' })
useEffect(() => {
console.log(atPage)
scrollItThere(atPage)
}, [atPage])
return (
<div onWheel={handleWheel} >
{pages.map((el, index) => <div
key={index}
ref={i => refs.current[index] = i}
onClick={() => refs.current[0]?.scrollIntoView({ block: 'start', behavior: 'smooth' })}
>
{el}
</div>)}
</div>
)
}
export default App
I'm having the same problem. scrollIntoView api doesn't work inside useEffect, while it fires by onClick. Worth mentioning that it works in Firefox. Here is the code:
import React, { useEffect, useRef, useState } from 'react' import FirstPage from './components/FirstPage' import SecondPage from './components/SecondPage' import ThirdPage from './components/ThirdPage' import './App.css' const App = () => { const [atPage, setAtPage] = useState<number>(0) const refs = useRef<(null | HTMLDivElement)[]>([]) const pages = [<FirstPage />, <SecondPage />, <ThirdPage />] const handleWheel = (event: any) => { console.log(event.deltaY > 0 ? 'down' : 'up') setAtPage(atPage => { let atPageCopied = JSON.parse(JSON.stringify(atPage)) return event.deltaY < 0 ? (atPage > 0 ? atPageCopied - 1 : atPage) : (atPage < pages.length - 1 ? atPageCopied + 1 : atPage) }) } const scrollItThere = (atPage: number) => refs.current[atPage]?.scrollIntoView({ block: 'start', behavior: 'smooth' }) useEffect(() => { console.log(atPage) scrollItThere(atPage) }, [atPage]) return ( <div onWheel={handleWheel} > {pages.map((el, index) => <div key={index} ref={i => refs.current[index] = i} onClick={() => refs.current[0]?.scrollIntoView({ block: 'start', behavior: 'smooth' })} > {el} </div>)} </div> ) } export default App
This works like a charm. Thanks buddy
Come accross this issue with
React: 18.2.0
Chrome: Version 108.0.5359.125 (Official Build) (64-bit)
FireFox Developer Edition 109.0b9
, that element.current.scrollIntoView
works in FF but not in Chrome, is there any progress?
I had the same problem So I solved this by using setTimeout. (The flicker looks weird, but it works.)
// move to todo post
useEffect(() => {
...
const timer = setTimeout(() => {
const todoPost = document.querySelector(`[data-post="${selectedTodoPost}"]`);
if (!todoPost) {
return;
}
todoPost.scrollIntoView({ block: 'center' });
}, 500);
return () => clearTimeout(timer);
}, [...]);
@hwangyena cool little thing I found out -- you can do what you did setTimeout(..., 0)
and it works as well without delay, at least in my use case.
It's basically just a trick to defer until next tick in JS.
I had the same problem and managed to fix it by using the scroll
method for the parent node instead of scrollIntoView
for the children node:
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const scrollToTheBottom = () => {
const scrollEl = ref.current;
scrollEl?.scroll({
top: scrollEl?.scrollHeight,
behavior: 'smooth',
});
};
scrollToTheBottom();
}, []);
return (
<Wrap>
<Scroll ref={ref}>
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
<MessageGroup messages={messagesIncome} />
</Scroll>
</Wrap>
);
Works fine in Firefox, Chrome, Safari, Edge
The problem doesn't seem to be limited to lastmessageRef.current
if you fetch the component by ID, it also does not work on chrome, but works fine in firefox.
Not working in Electron 19 (Chromium)
This was working in React 16, I've since updated the project to React 18
For me it works if I remove the behavior: 'smooth',
prop, however, I really want the smooth scrolling behaviour :(
@pjm4 Yeah, that was the case for me as well.
This is what I did as a workaround:
const [shouldScrollToRow, setShouldScrollToRow] = useState(false);
useEffect(() => {
if (shouldScrollToRow) {
setTimeout(() => {
elemRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 100);
setShouldScrollToRow(false);
}
}, [shouldScrollToRow]);
useEffect(() => {
// Listening for items getting added
if (itemsUpdated) {
setShouldScrollToRow(true);
setItemsUpdated(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemsUpdated]);
I want to know why this is happening
A workaround I've found it to use window.requestAnimationFrame
:
useEffect(() => {
if (itemsUpdated) {
window.requestAnimationFrame(() =>
elRef.current?.scrollIntoView()
)
}
}, [])
Just wanted to shed some light into this issue and why it can probably be closed.
This has nothing to do with react. This is a problem that is related to the chromium implementation of the scrollIntoView
method.
There's been some open issues for a long time in the chromium project regarding this issue.
https://bugs.chromium.org/p/chromium/issues/detail?id=1121151 https://bugs.chromium.org/p/chromium/issues/detail?id=1043933 https://bugs.chromium.org/p/chromium/issues/detail?id=833617
So from what I've got from all the testing and searching the last few days is that chromium based browsers interrupt the scrollIntoView
when any other scroll event is triggered.
This only happens with the scrollIntoView
method, the scrollTo
and scroll
methods don't suffer from this issue.
You can see and test this by yourself on the following fiddle.
https://jsfiddle.net/2bnspw8e/8/
So then what's a solution if you want to keep the simplicity of scrollIntoView
and have other scroll happen at the same time?
There is a ponyfill version of this method which reimplements it using the non broken scroll
method.
I hope this clarifies what's actually happening behind the scene and provides a solution while we all wait for a chromium fix.
Thanks for the digging and, I always wanted a one-trick pony!
This shows up at just the right time!
Cheers!
I ended up creating my own helper function.
export const smoothScroll = (targetY: number, duration: number) => {
const startingY = window.pageYOffset;
const diff = targetY - startingY;
let start: number;
window.requestAnimationFrame(function step(timestamp) {
if (!start) start = timestamp;
const time = timestamp - start;
const percent = Math.min(time / duration, 1);
window.scrollTo(0, startingY + diff * percent);
if (time < duration) {
window.requestAnimationFrame(step);
}
});
}
To call the function i use the following
if (viewerRef.current) {
const top = viewerRef.current.offsetTop;
smoothScroll(top, 500);
}
IMO almost all the answers above are not correct.
You are trying to invoke scrolling in Effect
but it should be done in LayoutEffect
.
Function inside effect executes in parallel to DOM update commits and in 99% it will be invoked faster. So normally real DOM (according to the vDOM built up with your render function) is not ready yet at that point of time. This is why "workarounds" using setTimeout
work somehow. But using LayoutEffect
is more accurate.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
this is not stale, because it is not fixed
This is not fixed for sure. behavior: 'smooth'
does work on FF but not on Chrome. I have codes to reproduce if you wish.
I want to add that the useRef
, useEffect
combo didn't work for me (no movement at all, because elementRef.current
wasn't defined until after the effect had run). Instead, I updated to useCallback
.
const elementRef = useCallback((domEl) => {
if (domEl !== null ) {
domEl.scrollIntoView({
behavior: 'smooth',
});
}
}, []);
...
<div ref={elementRef} >...</div>
Hello.
After checking different scenarios, I also concluded that there is an issue with the way Chrome behaves with scrollIntoView()
when you set the behavior to smooth
.
In my case, the scroll randomly stops at the start or middle of the scroll. A temporary solution for me would be to disable the smooth scroll for Chrome browsers.
const behavior = navigator.userAgent.includes('Chrome') ? 'auto' : 'smooth'
ref.current?.scrollIntoView({ behavior: behavior, block: 'start' })
In my case I`ve encountered weird behavior with simple onPress event with window.scrollTo({ top: scrollRef.current.offsetTop, behavior: 'smooth' }); So I've wrapped this call with setTimeout 0ms and it fixed unexpected behavior (stops mid scroll) completely.
Hello.
After checking different scenarios, I also concluded that there is an issue with the way Chrome behaves with
scrollIntoView()
when you set the behavior tosmooth
.In my case, the scroll randomly stops at the start or middle of the scroll. A temporary solution for me would be to disable the smooth scroll for Chrome browsers.
const behavior = navigator.userAgent.includes('Chrome') ? 'auto' : 'smooth' ref.current?.scrollIntoView({ behavior: behavior, block: 'start' })
This was the issue in my case. Thanks mate!
Any updates on this?
I still seem to be getting this exact issue on Chrome. Removing behaviour: smooth
works.
Any updates on this?
I still seem to be getting this exact issue on Chrome. Removing
behaviour: smooth
works.
You should probably look into related chrome issue instead. This is a chrome bug that has nothing to do with react.
https://issues.chromium.org/issues/325081538
The recent comment says they are changing how smooth in to view smooth work in chrome, which may fix this issue.
Still not working for me in either Chrome or Firefox on desktop.
I enclose an excerpt from some messaging functionality - users can open and close a chat window from a list of available conversations.
The first time I open the window, the content appears but the window doesn't scroll. If I then close and reopen the window it works as intended, as does posting new text to the window.
Note: The placement of scrollIntoView() is in two different places within these methods - this is intentionally done to test the behaviour; afaik both should work, neither does.
const scroll_position = useRef()
(...)
function GetMessageThread(thread_id){
axios.post("/api/get_message_thread", {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
withCredentials: true
},
body: JSON.stringify({"thread_id" : thread_id})
})
.then((response) => {
if (response.statusText == "OK"){
setMessageList(response.data['message_list'])
console.log(":: GetMessageThread :: Scrolling to bottom of window ")
console.log("-- scroll_position.current =", scroll_position.current)
scroll_position.current.scrollIntoView()
}
})
}
function NewMessage(e){
e.preventDefault();
var payload = {"message" : newMessage, "thread_id" : thread_id}
axios.post('/api/new_message', {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
withCredentials: true
},
body: JSON.stringify(payload)
}).then((response) =>{
if (response.statusText == "OK"){
console.log("response from new_message = OK")
}
})
setNewMessage('')
console.log(":: NewMessage :: Scrolling to bottom of window")
scroll_position.current.scrollIntoView({ behavior : 'smooth'})
}
return(
<>
(...)
<DialogContent>
{messageList.map((msg, i) =>
{
return (<ChatMessage key={i} messageObj={msg} />)
}
)}
<span id="scroll_pos" name="scroll_pos" ref={scroll_position}></span>
</DialogContent>
(...)
</>
)
Output in console, whether on first or subsequent opens, is as follows :
:: GetMessageThread :: Scrolling to bottom of window
-- scroll_position.current = <span id="scroll_pos" name="scroll_pos"></span>
Hey 🙋♂️
I have built a simple chatbot using react, and when the user sends a message i want to show the last message in the chatWindow component, so I used the following code:
It works as expected in edge and Firefox, but on chrome it is behaving weird llink-to-the-chatbot: https://karthik2265.github.io/worlds-best-chatbot/ github-repo-link: https://github.com/karthik2265/worlds-best-chatbot
Thank you