addEventListener('scroll') to scrollable <div /> using useRef - React
Solution 1
An easier approach for this particular use case might be to use the onScroll
prop and use the scrollTop
property from the event target
to figure out if you should hide the image or not.
Example
const { useState } = React;
const App = props => {
const [isLogoActive, setLogoActive] = useState(true);
const onScroll = e => {
setLogoActive(e.target.scrollTop < 100);
};
return (
<div onScroll={onScroll} style={{ height: 300, overflowY: "scroll" }}>
<p style={{ marginBottom: 200 }}>top</p>
<img
style={{
width: 100,
height: 100,
visibility: isLogoActive ? "visible" : "hidden"
}}
src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png"
/>
<p style={{ marginTop: 200 }}>bottom</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Solution 2
Here is the correct way to bind the addEventListener on div using useRef()
import React, { useState, useEffect, useCallback, useRef } from 'react';
function ScrollingWrapper(props) {
const [hasScrolledDiv, setScrolled] = useState(false);
const scrollContainer = useRef(null);
const onScroll = useCallback((event) => {
if(event.target.scrollTop > 125){
setScrolled(true);
} else if(event.target.scrollTop < 125) {
setScrolled(false);
}
}, []);
useEffect(() => {
scrollContainerWrapper.current.addEventListener('scroll', onScroll);
return () => scrollContainerWrapper.current.removeEventListener('scroll', onScroll);
},[]);
return (
<div ref={scrollContainerWrapper}>
{props.children}
</div>
);
}
export default ScrollingWrapper;
Solution 3
Depending on your use case, it's usually also good to throttle scroll event listeners, so they don't run on every pixel change.
const App = props => {
const [isLogoActive, setLogoActive] = useState(true);
const onScroll = useMemo(() => {
const throttled = throttle(e => setLogoActive(e.target.scrollTop < 100), 300);
return e => {
e.persist();
return throttled(e);
};
}, []);
return (
<div onScroll={onScroll}>
<img
style={{ visibility: isLogoActive ? 'visible' : 'hidden' }}
src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png"
/>
</div>
);
};
The throttle
function is available in lodash.
Sam Kelham
Updated on June 15, 2022Comments
-
Sam Kelham almost 2 years
This is one of the first times I am actually using React Hooks properly in a project so bear with me if I am not quite there.
In the component below, my aim is to display the
<HelperTooltip>
on load and when the scrolling div (not the window) scrolls I want to hide after it scrolls X amount of pixels.My thought process is to create a useRef object on the scrolling
<div/>
element, which then I can add an event listens with a callback function which then can toggle the state to hide the<HelperTooltip>
I have created a Codesandbox below to try and demonstrate what I am trying to do. As you can see in the demo the
node.addEventListener('click')
is working fine, however when I try and call thenode.addEventListener('scroll')
it is not firing.I'm not sure if I taking the wrong approach or not, any help will greatly be appreciated. In the codesandbox demo it is the react image that I trying to hide on scroll, not the
<HelperTooltip>
CodeSandbox link: https://codesandbox.io/s/zxj322ln24
import React, { useRef, useCallback, useState } from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const App = props => { const [isLogoActive, toggleLogo] = useState(true); const scrollElementRef = useCallback(node => { node.addEventListener("click", event => { console.log("clicked", event); }); /* I want to add the scroll event listener here and the set the state isLogoActive to false like the event listener above but the 'scroll' event is firing --- see below on line 21 */ // node.addEventListener("scroll", event => { // console.log("scrolled", event); // toggle log // }); }); return ( <div className="scrolling-container"> <div ref={scrollElementRef} className="scrolling-element"> <p>top</p> {isLogoActive && ( <div className="element-to-hide-after-scroll"> <img style={{ width: "100px", height: "100px" }} src="https://arcweb.co/wp-content/uploads/2016/10/react-logo-1000-transparent-768x768.png" /> </div> )} <p>bottom</p> </div> </div> ); }; ReactDOM.render(<App />, document.getElementById("app"));
-
Sam Kelham about 5 yearsYep great spot about putting the ref on the wrong element! and thank you for giving me a comprehensive answer, works a treat. I will have to do some reading on the useEffect hook and see what it is all about. Thank you