Code:
export const HomePage = (): JSX.Element => {
const refContainer = useRef<HTMLDivElement>(null);
const [scrollY, setScrollY] = useState<number>(0);
const { current: elContainer } = refContainer;
const handleScroll = useCallback(() => {
if (elContainer) setScrollY(elContainer.scrollTop);
}, []);
useEffect(() => {
document.addEventListener("scroll", handleScroll, { passive: true });
return () => removeEventListener("scroll", handleScroll);
}, [handleScroll]);
return (
<div className="pageScreen overflow-scroll" ref={refContainer}>
<Works scrollY={scrollY} />
</div>
);
};
ScrollY state doesn’t change, because elContainer is null. How can i fix that? Thanks.
3
Answers
The use of
useCallback
memoizes the scroll handler, causing it to reference a stale value ofelContainer
, particularly when it wasnull
on the initial render.Additionally, an element’s
scrollTop
only changes when the element’s content itself is scrolled, not the whole document. As a result, thescroll
event handler should be attached to thediv
, allowing you greatly simplify your code, like so:Just note that for the event to work, the
div
itself needs to be able scroll, not the document, so you also need to style yourdiv
withoverflow: auto
and amax-height
.Here is a CodeSandbox demo of this in action.
You are extracting the
.current
property from the ref during the very first render, and saving it in a variableelContainer
. This value is null, because nothing is on the page yet. And since you only set up the scroll event once, handle scroll is forever using this null value.Instead, don’t access
.current
until handleScroll is called. That way, the first render will already be complete, and refContainer.current will have been mutated to have the element:This should work :
Overall, the proposed code is more concise, easier to read, and more reliable. It uses the
FC
type to define the component, which is a shorthand forFunctionComponent
. It uses thegetBoundingClientRect
method to get the scroll position, which is more accurate than usingscrollTop
. And it usesdocument.removeEventListener
to remove the event listener on unmount, which is more specific and less prone to errors.