chatscope / chat-ui-kit-react

Build your own chat UI with React components in few minutes. Chat UI Kit from chatscope is an open source UI toolkit for developing web chat applications.
https://chatscope.io
MIT License
1.34k stars 116 forks source link

How to close loadingMore status when no more messages? #77

Open jazwu opened 2 years ago

jazwu commented 2 years ago

I have refered to the code in https://chatscope.io/storybook/react/?path=/docs/components-messagelist--loading-more-state and changed the fake fetch part, receiving loaded messages in batches from my own server whenever I scroll the bar to the top. But I have difficulty stopping loading more messages when there are no more messages (aka. this.state.receivedloadMsgList === [] ), and it failed to work as below.

image

The loadingMore circle constantly appears and it is just stuck here. Meanwhile, my last batch of loaded messages doesn't show either ( which is [1, 2, 3, 4] ).

Does anyone know how to stop executing onYReachStart function when there are no more loaded messages? Also, how can I close loadingMore status? THANKS!

Here is part of my code:

onYReachStart(e) {
        if (this.state.loadingMore == true) {
            return;
        }

        if (this.state.receivedloadMsgList != []) {
            this.setState({loadingMore: true});
        } else {
            this.setState({loadingMore: false});return;    
        }

        setTimeout(() => {
            let messages = [];

            if (this.state.receivedloadMsgList !== []) {

                let list = this.state.receivedloadMsgList; // loaded messages from the server

                list.reverse().forEach((val, i) => {
                    let position;

                    if (val.mode1 == 'sysmessage') {
                        messages.push(<MessageSeparator content={this.showSysDate(val.information)} />)
                    }else if (val.sender == this.state.ownner.id) { position = 'outgoing' }
                    else { position = 'incoming' }

                    if (val.mode1 == 'text') {
                        messages.push(
                            <Message model={{ direction: position }}>
                                <Message.TextContent text={val.information} />
                            </Message>
                        )
                        this.setState({counter: this.state.counter+1});
                    }
                    if (val.mode1 == 'photo') {
                        messages.push(
                            <Message model={{ position: position }}>
                                <Message.CustomContent src={val.information} width={300}>
                                   ...
                                </Message.CustomContent>
                            </Message>
                        )
                        this.setState({counter: this.state.counter+1})
                    }
                    if (val.mode1 == 'file') {
                        messages.push(
                            <Message model={{ position: position }}>
                                <Message.CustomContent>
                                   ...
                                </Message.CustomContent>
                            </Message>
                        )         
                    }   
                })
                this.setState({loadedMsgList: messages.reverse().concat(this.state.loadedMsgList)});
                this.setState({loadingMore: false});
            }
        }, 1500);
supersnager commented 2 years ago

@jazwu Thanks for the question. I think the problem is here:

this.state.receivedloadMsgList != []

This is because you compare references, that are always different. For example, check this in the browser console:

[] !== []

This is exactly the same as what you are doing. So your condition will always be true. Please try the following condition instead:

this.state.receivedloadMsgList.length !== 0
jazwu commented 2 years ago

@supersnager Thanks for your help. I changed all the if (this.state.receivedloadMsgList !== []) to if (this.state.receivedloadMsgList !== 0). But when the bar reaches the top and there are no more messages, it just cannot stop. Even if I scroll it down, the loading is still in progress image until the error occurs image

supersnager commented 2 years ago

@jazwu I need more code to check this. Please prepare a minimal repro.

jazwu commented 2 years ago

@supersnager here is part of my code, and I mocked the data. https://codesandbox.io/s/serene-cookies-17o0jh?file=/src/Chatroom/index.tsx&resolutionWidth=320&resolutionHeight=675

jazwu commented 2 years ago

@supersnager server.py sends a batch of messages whenever the bar reaches the top. index.tsx receives these batches one by one and shows them

supersnager commented 2 years ago

@jazwu Sorry, but the sample doesn't work at all, so I'm not able to check it thoroughly. However here are my thoughts:

The flow should be as follows:

  1. User scrolls to the top
  2. onYReachStart sets the loadingMore flag to true and sends a request to the server for older messages
  3. Messages are received from the server (socket.onmessage)
  4. Add received messages to the state. If the received array is empty set the loadingMore flag to false, and set the additional flag noMoreMessages to true to prevent downloading messages.
onYReachStart() {
  if ( this.state.loadingMore || this.state.noMoreMessages ) {
    return;
  }

  socket.send(JSON.stringify({ mode: "history1", num: this.state.loadedMsgList.length }));
}

Additionally, when your server sends the last page of messages it can also send the flag that there are no more messages, so you don't need another request to get an empty array.

jazwu commented 2 years ago

@supersnager Thanks for your help. I tried your method but failed, so I was considering the issue was about the asynchronous mechanism. Now I have changed my way of getting loaded messages using HTTP, it works perfectly when there are no more messages. However, a new issue comes out. Whenever I scroll the bar to the top just once, I receive 9 batches of loaded messages, not one batch.

https://user-images.githubusercontent.com/113967503/202662459-2a825e69-5200-4930-b7c4-040376bccb8d.mp4

jazwu commented 2 years ago

@supersnager My code now is as below

async onYReachStart(e) {
        if (this.state.loadingMore) {
            return;
        }

        if (this.state.noMoreMessages === false) { 
            var messages = [];
            await fetch('http://192.168.2.94:8585/httpserver/testing', {
                method: 'POST',
                headers: new Headers({'content-type' : 'application/json'}),
                body: JSON.stringify({
                    mode: 'history1',
                    id: this.state.portId,
                    num: this.state.counter
                })
            })
            .then(res => res.json())
            .then (data => {
                console.log(data);
                const received_loaded_messages = data.information;
                this.setState({noMoreMessages: data.no_more_message});

                let list = received_loaded_messages;
                list.reverse().forEach((val, i) => {
                    let position;

                    if (val.mode1 === 'text') {
                        messages.push(
                            <Message model={{ direction: position }}>
                                <Message.TextContent text={val.information} />
                            </Message>
                        )
                    }       
                })
                this.setState({
                    loadedMsgList: messages.reverse().concat(this.state.loadedMsgList),
                    loadingMore: false,
                    counter: this.state.counter+received_loaded_messages.length-1
                });
            })
            .catch(function (error) {
            console.log(error);
        });
        } else {return;}
    }