ankeetmaini/react-infinite-scroll-component

next fucntion is not called when scrollableTarget was used in antd Modal width react 18

7NZ opened this issue · 12 comments

7NZ commented

I use react-infinite-scroll-component in antd's Modal component.

I want to scroll list to the top in some scenario, So I used scrollableTarget prop and use ref on target div element.
But the next function wasn't called when I scroll list to the bottom.

example: https://codesandbox.io/s/wonderful-orla-58offh?file=/src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import InfiniteScroll from "react-infinite-scroll-component";
import { Button, Modal } from "antd";
import "antd/dist/antd.css";

const style = {
  height: 30,
  border: "1px solid green",
  margin: 6,
  padding: 8
};

function App() {
  const [items, setItems] = React.useState(Array.from({ length: 20 }));
  const [showDialog, setShowDialog] = React.useState(false);

  const openDialog = () => {
    setShowDialog(true);
  };

  const hideDialog = () => {
    setShowDialog(false);
  };

  const fetchMoreData = () => {
    // a fake async api call like which sends
    // 20 more records in 1.5 secs
    setTimeout(() => {
      setItems(items.concat(Array.from({ length: 20 })));
    }, 1500);
  };

  return (
    <>
      <h1>demo: react-infinite-scroll-component</h1>
      <hr />
      <Button onClick={openDialog}>open dialog</Button>
      <Modal
        title="infinite-scroll"
        open={showDialog}
        width="80%"
        onCancel={hideDialog}
      >
        <div id="scrollableDiv" style={{ height: 300, overflow: "auto" }}>
          <InfiniteScroll
            dataLength={items.length}
            next={fetchMoreData}
            hasMore={true}
            loader={<h4>Loading...</h4>}
            scrollableTarget="scrollableDiv"
          >
            {items.map((i, index) => (
              <div style={style} key={index}>
                div - #{index}
              </div>
            ))}
          </InfiniteScroll>
        </div>
      </Modal>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(<App />);

package.json

{
  "name": "new",
  "version": "1.0.0",
  "description": "",
  "keywords": [],
  "main": "src/index.js",
  "dependencies": {
    "antd": "4.24.2",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-infinite-scroll-component": "4.1.0",
    "react-scripts": "1.1.4"
  },
  "devDependencies": {},
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

Hello,did you solve this problem?

7NZ commented

@liulib I use className to querySelector element instead scrollableTarget. so that the next function can work right.

const scollList = document.querySelector('.js-scroll-list');
if (scollList) {
    scollList.scrollTo(0, 0);
}

<InfiniteScroll
  className="js-scroll-list"
>
  ...
</InfiniteScroll>

@7NZ Thanks. Have a good day.

@7NZ Thanks for the solution 👍

This is not working. Could anyone tell me in brief.

`import React, { useEffect } from "react";
import ReactDOM from "react-dom/client";
import InfiniteScroll from "react-infinite-scroll-component";
import { Button, Modal } from "antd";
import "antd/dist/antd.css";

const style = {
height: 30,
border: "1px solid green",
margin: 6,
padding: 8
};

function App() {
const [items, setItems] = React.useState(Array.from({ length: 20 }));
const [showDialog, setShowDialog] = React.useState(false);

const openDialog = () => {
setShowDialog(true);
};

const hideDialog = () => {
setShowDialog(false);
};

const fetchMoreData = () => {
// a fake async api call like which sends
// 20 more records in 1.5 secs
setTimeout(() => {
setItems(items.concat(Array.from({ length: 20 })));
}, 1500);
};

useEffect(() => {
const scollList = document.querySelector(".js-scroll-list");
if (scollList) {
scollList.scrollTo(0, 0);
}
}, []);

return (
<>

demo: react-infinite-scroll-component




open dialog

<div id="scrollableDiv" style={{ height: 300, overflow: "auto" }}>
<InfiniteScroll
dataLength={items.length}
next={fetchMoreData}
hasMore={true}
loader={

Loading...

}
scrollableTarget="scrollableDiv"
className="js-scroll-list"
>
{items.map((i, index) => (

div - #{index}

))}



</>
);
}

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render();
`

@liulib I use className to querySelector element instead scrollableTarget. so that the next function can work right.

const scollList = document.querySelector('.js-scroll-list');
if (scollList) {
    scollList.scrollTo(0, 0);
}

<InfiniteScroll
  className="js-scroll-list"
>
  ...
</InfiniteScroll>
7NZ commented

@vamsimudadla Don't use scrollableTarget property, use querySelector do whatever you want when dialog opened.

@7NZ
can you please make changes to make in given code sandbox example and please share it over here if possible,
because i am not getting how to implement it.

@7NZ can you please make changes to make in given code sandbox example and please share it over here if possible, because i am not getting how to implement it.

Hello!
use a fixed height

<InfiniteScroll
scrollableTarget="scrollableDiv"
height={200}

@7NZ can you please make changes to make in given code sandbox example and please share it over here if possible, because i am not getting how to implement it.

Hello! use a fixed height

<InfiniteScroll scrollableTarget="scrollableDiv" height={200}

Thank you very much bro!!!

This works for me.
Give InfiniteScroll a fixed height, and use handleRef function resize height when dialog's height changed.

const [scrollHeight, setScrollHeight] = useState(300);
const handleRef = (node: HTMLDivElement | null) => {
  if(node) {
    const height = node.scrollHeight
    setScrollHeight(height)
  }
}

return (
  <Dialog open={open} onOpenChange={setOpen}>
    <DialogTrigger asChild>
      <Button variant="ghost">
        <HistoryIcon width={16} className="mr-1" />
        History List
      </Button>
    </DialogTrigger>
    <DialogContent className="flex flex-col max-w-4xl h-[90vh]">
      <DialogHeader>
        <DialogTitle>History List</DialogTitle>
        <DialogDescription>
          Make changes to your profile here. Click save when you're done.
        </DialogDescription>
      </DialogHeader>
      <div
        ref={node => {
          handleRef(node)
        }}
        className="flex-1 overflow-y-auto"
      >
        <InfiniteScroll
          height={scrollHeight}
          dataLength={questions.length}
          next={fetchMoreData}
          hasMore={true}
          loader={<SkeletionContent count={2} />}
          endMessage={<p className="text-center text-sm text-muted-foreground">All Loaded.</p>}
        >
          {questions.map((question, index) => ( <div>test</div>))}
        </InfiniteScroll>
      </div>
    </DialogContent>
  </Dialog>
)

ScrollableTarget not working with Drawer of ant design. Can someone help me solve this problem?

const NotificationComponent = () => {
    const [connectionHub, setConnectionHub] = useState<HubConnection | undefined>(undefined);
    const [notifications, setNotifications] = useState<INotification[]>([]);
    const [pageIndex, setPageIndex] = useState <number>(1);
    const [total, setTotal] = useState <number>(0);
    const [hasMore, setHasMore] = useState <boolean>(false);
    const [loading, setLoading] = useState(false);
    const [open, setOpen] = useState(false);
    const [totalUnread, setTotalUnread] = useState <number>(0);
    const intl = useIntl();

    useEffect(() => {
      const connectToHub = async () => {
        const user = await getUser();
        const hub = new HubConnectionBuilder()
          .withUrl(`${GATEWAY}/hub/notification`, {
            skipNegotiation: true,
            transport: HttpTransportType.WebSockets,
            accessTokenFactory: () => user?.access_token ?? '',
          })
          .build();
        setConnectionHub(hub);
        hub
          .start()
          .then(() => {
          })
          .catch((error) => {
            console.error('Error starting SignalR connection:', error);
          });
      };

      const loadData = async () => {
        setLoading(true);
        const response = await GetNotification({});
        if (response.success) {
          setNotifications(response.data);
          setPageIndex(response.current);
          setTotal(response.total);
          setTotalUnread(response.dataExtend.totalUnread);
          setHasMore(response.dataExtend.hasMore);
        }
        setLoading(false);
      };
      loadData()
      connectToHub();
    }, []);

    const onReceiveData = (data: any) => {
      notifyUser(data.title, data.message);
      setTotal(total + 1);
      setTotalUnread(totalUnread + 1);
    }

    useEffect(() => {
      if (connectionHub) {
        connectionHub.on('ReceiveNotification', (data: any) => {
            setNotifications((prev) => [data,...prev]);
            onReceiveData(data)
        });
      }
    }, [connectionHub])

  const loadMoreData = async () => {
      alert('loading more data');
      if (loading) return;
      const nextPage = pageIndex + 1;
      setLoading(true);
      const response = await GetNotification({ current: nextPage });
      if (response.success) {
        setNotifications((prev) => [...prev,response.data]);
        setPageIndex(response.current);
        setTotal(response.total);
        setTotalUnread(response.dataExtend.totalUnread);
        setHasMore(response.dataExtend.hasMore);
      }
      setLoading(false);
    };
  return (
    <>
      <div 
      style={{
          display: 'flex',
          height: 26,
      }}
      onClick={() => setOpen(true)}
      >
        <Badge count={totalUnread}>
          <BellOutlined />
        </Badge>
      </div>
      <Drawer id="scrollableNotification" title={intl.formatMessage({
                id: 'component.notification.title',
                defaultMessage: 'Notification',
            })} 
            onClose={() => setOpen(false)} 
            open={open}
            style={{overflow: 'auto' }}
            >
                <InfiniteScroll
                  dataLength={notifications.length}
                  next={loadMoreData}
                  hasMore={ hasMore}
                  loader={<Skeleton avatar paragraph={{ rows: 1 }} active />}
                  endMessage={<Divider plain>It is all, nothing more 🤐</Divider>}
                  scrollableTarget={'scrollableNotification'}
                  key={'id'}
                >
                  <List
                    dataSource={notifications}
                      renderItem={(item) => (
                        <List.Item key={item.userName} onClick={() => { console.log(item) }} style={{ cursor: 'pointer' }}>
                        <List.Item.Meta
                          avatar={<Avatar src={item.icon} />}
                          title={<span className={item.status === 1 ? 'title-bold' : 'title-default'}>{item.message}</span>}
                          description={getTimeAgo(new Date())}
                          key={item.id}
                          />
                          <div>{item.status === 1 ? <Badge className="custom-badge" key={item.status} size={'default'} color={'blue'} /> : <Badge key={item.status} color={''} />}</div>
                      </List.Item>
                    )}
                  />
                  </InfiniteScroll>
      </Drawer>
    </>
  );
};
  
export default NotificationComponent;