karl-run / react-bottom-scroll-listener

A simple listener component that invokes a callback when the webpage is scrolled to the bottom.
https://karl-run.github.io/react-bottom-scroll-listener
149 stars 15 forks source link

Bottom Scroll Listener with tabs #43

Closed Nikita279 closed 3 years ago

Nikita279 commented 3 years ago

Hi,

I have 2 tabs on a page, each has its own pagination and hence has a Bottom Scroll Listener, each. Issue is, when I reach the bottom of tab A, it hits the onBottom function of tab B as well, and vice versa. How can I have 2 separate independent listeners on each tab?

My pseudo code structure is - Parent Class -

<Tab A>
        <ChildClass></ChildClass>
</Tab A>

<Tab B>
        <ChildClass></ChildClass>
</Tab B>

Child Class -

<BottomScrollListener onBottom={this.fetchMorePosts}>
         <Posts></Posts>
</BottomScrollListener>
karl-run commented 3 years ago

When I set up the example project to use two containers it worked as expected. Are you sure you are passing in the ref correctly?

Here's the full modified example:

const ComponentExample = () => {
  const handleContainerOnBottom = () => {
    console.log('I am at bottom in first optional container! ' + Math.round(performance.now()));

    alert('Bottom of first container hit! Too slow? Reduce "debounce" value in props');
  };

  const handleContainerOnBottomSecond = () => {
    console.log('I am at bottom in second optional container! ' + Math.round(performance.now()));

    alert('Bottom of second container hit! Too slow? Reduce "debounce" value in props');
  };

  return (
    <div className="root">
      <BottomScrollListener onBottom={handleContainerOnBottom}>
        {(scrollRef) => (
          <div ref={scrollRef} className="inner-scroll-example">
            <h4>Callback when this container hits bottom</h4>
            <div>Scroll down! ▼▼▼</div>
            <div>A bit more... ▼▼</div>
            <div>Almost there... ▼</div>
            <div>You've reached the bottom!</div>
          </div>
        )}
      </BottomScrollListener>
      <BottomScrollListener onBottom={handleContainerOnBottomSecond}>
        {(scrollRef) => (
          <div ref={scrollRef} className="inner-scroll-example inner-scroll-example2">
            <h4>Callback when this container hits bottom</h4>
            <div>Scroll down! ▼▼▼</div>
            <div>A bit more... ▼▼</div>
            <div>Almost there... ▼</div>
            <div>You've reached the bottom!</div>
          </div>
        )}
      </BottomScrollListener>
    </div>
  );
};

The hooks should also work this way, since the component is using the hook under the hood.

karl-run commented 3 years ago

I re-read your issue, and it seems like you maybe are using a global bottom scroll listener (one that doesn't provide any refs). If that's the case it will attach it self to the document, which means that if both of your tabs are mounted at the same time, but event listeners will be triggered when the document hits bottom.

I would either make sure your non-visible tabs are unmounted, or if that is not possible, you might have to elevate the onBottom listener above the tabs.

Nikita279 commented 3 years ago

Hey @karl-run , thank you for your response.

In my case, when page loads, it mounts the visible tab only at first, then on hitting 2nd tab it mounts that 2nd tab also, but doesn't unmount the 1st tab.

I tried the reference method, but when I add ref=scrollRef, it doesn't call onBottom function at all, in either tabs. For both tabs, the child html is identical in my case. So I thought maybe the reference is conflicting. So to copy your example I added className as active tab key, to differ between the two child ref div within tabs. But it didn't work.

Here's my code structure -

Parent Class -

import React, { Component } from "react";
import { Tabs } from "antd";
import ChildClass from "./ChildClass";

const { TabPane } = Tabs;

class TabsContainer extends Component {
  render() {
    return (
      <Tabs onChange={(e) => this.setState({ activeTabKey: e })}>
          <TabPane tab="A" key="A">
              <ChildClass tabKey="A"></ChildClass>
          </TabPane>
          <TabPane tab="B" key="B">
              <ChildClass tabKey="B"></ChildClass>
          </TabPane>
      </Tabs>
    );
  }
}

export default TabsContainer;

Child Class -

class ChildClass extends Component {
      constructor(props) {
            super(props);

            this.state = {
                  activeTabKey: props.tabKey,
            };
    }

    fetchMorePosts = async () => {
              console.log("I'm at bottom");

              if (this.state.activeTabKey == "A") {
                     console.log("I'm at bottom of Tab A");
                     //http call to get data for tab A
              }

              if (this.state.activeTabKey == "B") {
                     console.log("I'm at bottom of Tab B");
                     //http call to get data for tab B
              }
    }

    render() {
        return (
            <BottomScrollListener onBottom={this.fetchMorePosts}>
                {(scrollRef) => (
                    <div ref={scrollRef} className={this.state.activeTabKey}>
                        <Some code to render data, set via fetchMorePosts function>
                    </div>
                )}
           </BottomScrollListener>
       );
    }
}

If I remove ref={scrollRef}, on scrolling at bottom of either tab (when both are mounted) I get output as -

     I'm at bottom
     I'm at bottom of Tab A
     I'm at bottom
     I'm at bottom of Tab B

Though my data is being rendered correctly in each tab respectively. Only issue is performance related, as 2 http calls are going on hitting bottom of either tab. Both tabs are getting paginated together.

If I keep ref={scrollRef}, on scrolling at bottom of either tab I get nothing in console (fetchMorePosts not called at all)-

     --

What am I doing wrong??

karl-run commented 3 years ago

Look at BottomScrollListener without a ref as a BottomOfDocumentScrollListener, it's inherently global. If you provide a ref you'll have to provide a ref to the HTML-element that has overflow, i.e. some kind of inner scroll (look at the demo page and you'll understand), but your app has overflow on the document, and you have multiple "global" listeners.

A quick fix would be to pass the activeTabKey to each tab and have them only update if their own key match the active key. It's a bit dirty, but I don't know antd enough to help you. In any case it's not really an issue with the library.

Nikita279 commented 3 years ago

Ok. Thanks @karl-run . Your insight has been helpful.

karl-run commented 3 years ago

No problem. I'll close this for now. But if you have any suggestions on how the library API might be improved, feel free to open another one.