8

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 the node.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"));
1
  • maybe you can use useEffect hook instead of useCallback Commented Mar 26, 2019 at 11:45

4 Answers 4

7

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>

Sign up to request clarification or add additional context in comments.

Comments

2

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.

Comments

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(() => {
    scrollContainer.current.addEventListener('scroll', onScroll);
    return () => scrollContainer.current.removeEventListener('scroll', onScroll);
},[]);

return (
    <div ref={scrollContainer}>
        {props.children}
    </div>
  );
 }

 export default ScrollingWrapper;

Comments

0

In your example, the scroll is not triggered on the scrolling-element but on the scrolling-container so that's where you want to put your ref : https://codesandbox.io/s/ko4vm93moo :)

But as Throlle said, you could also use the onScroll prop !

1 Comment

Yep 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.