反应Chatkit API。 MessageList组件错误-渲染来自其他房间的消息。组件生命周期和状态

时间:2018-10-18 06:20:21

标签: javascript reactjs redux chat chatkit

我使用React构建的Chatkit应用程序正在经历一些奇怪的活动。本质上,我正在不同房间中的两个不同用户进行测试。当我从一个房间中的用户发送消息时。尽管其他用户不在同一个房间中,但其他用户仍可以看到该消息。这是正在发生的事情的屏幕截图。

仅当用户至少在同一个房间中一次时,这种情况才会发生。

Buggy Chat

我可以告诉他们消息是正确创建的,因为我在ChatKit API中的正确位置看到了它们。另外,如果我重新渲染该组件,则消息最终会显示在正确的位置。但是跨房间消息传递错误仍然存​​在。

Corrected Chat

我的印象是,它肯定与MessageList组件的状态有关。我确保每次进入新房间时都会更新组件状态,但是我想真正的问题是,应用程序的其他实例是否还要关心其他实例的组件状态更改。

因此,事不宜迟,这是我的代码:

ChatScreen(主应用程序)

import React from "react"
import Chatkit from "@pusher/chatkit"
import MessageList from "./MessageList"
import SendMessageForm from "./SendMessageForm"
import WhosOnlineList from "./WhosOnlineList"
import RoomList from "./RoomList"
import NewRoomForm from "./NewRoomForm"
import { getCurrentRoom } from "../../actions/chatkitActions"
import { connect } from "react-redux"

class ChatScreen extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            messages: [],
            currentRoom: {},
            currentUser: {},
            usersWhoAreTyping: [],
            joinableRooms: [],
            joinedRooms: [],
            errors: {}
        }

        this.sendMessage = this.sendMessage.bind(this)
        this.sendTypingEvent = this.sendTypingEvent.bind(this)
        this.subscribeToRoom = this.subscribeToRoom.bind(this)
        this.getRooms = this.getRooms.bind(this)
        this.createRoom = this.createRoom.bind(this)
    }

    componentDidMount(){
        //setup Chatkit
        let tokenUrl
        let instanceLocator = "somecode"
        if(process.env.NODE_ENV === "production"){
            tokenUrl = "somenedpoint"
        } else {
            tokenUrl = "http://localhost:3000/api/channels/authenticate"
        }

        const chatManager = new Chatkit.ChatManager({
            instanceLocator: instanceLocator,
            userId: this.props.chatUser.name,
            connectionTimeout: 120000,
            tokenProvider: new Chatkit.TokenProvider({
                url: tokenUrl
            })
        })

        //initiate Chatkit
        chatManager.connect()
            .then((currentUser) => {
                this.setState({
                    currentUser: currentUser
                })
                //get all rooms
                this.getRooms()

                // if the user is returning to the chat, direct them to the room they last visited
                if(this.props.chatkit.currentRoom.id > 0){
                    this.subscribeToRoom(this.props.chatkit.currentRoom.id)
                }
            })
    }

    sendMessage = (text) => {
        this.state.currentUser.sendMessage({
            roomId: this.state.currentRoom.id,
            text: text
        })
    }

    sendTypingEvent = () => {
        this.state.currentUser
            .isTypingIn({
                roomId: this.state.currentRoom.id
            })
            .catch((errors) => {
                this.setState({
                    errors: errors
                })
            })
    }

    getRooms = () => {
        this.state.currentUser.getJoinableRooms()
            .then((joinableRooms) => {
                this.setState({
                    joinableRooms: joinableRooms,
                    joinedRooms: this.state.currentUser.rooms
                })
            })
            .catch((errors) => {
                this.setState({
                    errors: { error: "could not retrieve rooms"}
                })
            })
    }

    subscribeToRoom = (roomId) => {
        this.setState({
            messages: []
        })
        this.state.currentUser.subscribeToRoom({
            roomId: roomId,
            hooks: {
                onNewMessage: (message) => {
                    this.setState({
                        messages: [...this.state.messages, message]
                    })
                },
                onUserStartedTyping: (currentUser) => {
                    this.setState({
                        usersWhoAreTyping: [...this.state.usersWhoAreTyping, currentUser.name]
                    })
                },
                onUserStoppedTyping: (currentUser) => {
                    this.setState({
                        usersWhoAreTyping: this.state.usersWhoAreTyping.filter((user) => {
                            return user !== currentUser.name
                        })
                    })
                },
                onUserCameOnline: () => this.forceUpdate(),
                onUserWentOffline: () => this.forceUpdate(),
                onUserJoined: () => this.forceUpdate()
            }           
        })
        .then((currentRoom) => {
            this.setState({
                currentRoom: currentRoom
            })
            this.getRooms()
            //store currentRoom in redux state
            this.props.getCurrentRoom(currentRoom)
        })
        .catch((errors) => {
            this.setState({
                errors: errors
            })
        })
    }

    createRoom = (roomName) => {
        this.state.currentUser.createRoom({
            name: roomName
        })
        .then((newRoom) => {
            this.subscribeToRoom(newRoom.id)
        })
        .catch((errors) => {
            this.setState({
                errors: { error: "could not create room" }
            })
        })
    }

    render(){
        const username = this.props.chatUser.name
        return(
            <div className="container" style={{ display: "flex", fontFamily: "Montserrat", height: "100vh"}}>
                <div 
                    className="col-md-3 bg-dark mr-2 p-0" 
                    style={{display: "flex", flexDirection: "column", maxHeight: "80vh", padding: "24px 24px 0px"}}
                >
                    <div style={{flex: "1"}} className="p-4">
                        <WhosOnlineList users={this.state.currentRoom.users}/>
                        <RoomList
                            roomId={this.state.currentRoom.id} 
                            rooms={[...this.state.joinedRooms, ...this.state.joinableRooms]}
                            subscribeToRoom={this.subscribeToRoom}
                        />
                    </div>
                    <NewRoomForm createRoom={this.createRoom} user={this.state.currentUser}/>
                </div>

                <div 
                    className="col-md-9 border p-0" 
                    style={{display: "flex", flexDirection: "column", maxHeight: "80vh"}}
                >
                    <div className="mb-3">
                        { this.state.currentRoom.name ? (
                            <h4 
                                className="bg-black text-light m-0" 
                                style={{padding: "1.0rem 1.2rem"}}
                            >
                                {this.state.currentRoom.name}
                            </h4>
                            ) : ( 
                            this.props.chatkit.currentRoom.id > 0 ) ? ( 
                                <h3 className="text-dark p-4">Returning to room...</h3>
                            ) : (
                                <h3 className="text-dark p-4">&larr; Join a Room!</h3>
                        )}
                    </div>
                    <div style={{flex: "1"}}>
                        <MessageList messages={this.state.messages} room={this.state.currentRoom.id} usersWhoAreTyping={this.state.usersWhoAreTyping}/>
                    </div>
                    <SendMessageForm 
                        sendMessage={this.sendMessage}
                        userTyping={this.sendTypingEvent} 
                        currentRoom={this.state.currentRoom}
                    />
                </div>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return{
        chatkit: state.chatkit
    }
}

const mapDispatchToProps = (dispatch) => {
    return{
        getCurrentRoom: (currentRoom) => {
            dispatch(getCurrentRoom(currentRoom))
        }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(ChatScreen)

MessageList(组件)

import React from "react"
import ReactDOM from "react-dom"
import TypingIndicator from "./TypingIndicator"

class MessageList extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            currentRoom: {}
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.room){
            console.log(nextProps.room)
            this.setState({
                currentRoom: nextProps.room
            })
        }
    }

    componentWillUpdate(){
        const node = ReactDOM.findDOMNode(this)
        //scrollTop is the distance from the top. clientHeight is the visible height. scrollHeight is the height on the component
        this.shouldScrollToBottom = node.scrollTop + node.clientHeight + 100 >= node.scrollHeight
    }

    componentDidUpdate(){
        //scroll to the bottom if we are close to the bottom of the component
        if(this.shouldScrollToBottom){
            const node = ReactDOM.findDOMNode(this)
            node.scrollTop = node.scrollHeight
        }
    }

    render(){
        const messages = this.props.messages
        let updatedMessages = []
        for(var i = 0; i < messages.length; i++){
            let previous = {}
            if(i > 0){
                previous = messages[i - 1]
            }
            if(messages[i].senderId === previous.senderId){
                updatedMessages.push({...messages[i], senderId: ""})
            } else{
                updatedMessages.push(messages[i])
            }
        }
        return(
            <div>
                {this.props.room && (
                    <div style={{overflow: "scroll", overflowX: "hidden", maxHeight: "65vh"}}>
                        <ul style={{listStyle: "none"}} className="p-3">
                            {updatedMessages.map((message, index) => {
                                return (
                                    <li className="mb-1" key={index}>
                                        <div>
                                            {message.senderId && (
                                                <span 
                                                    className="text-dark d-block font-weight-bold mt-3"
                                                >
                                                    {message.senderId}
                                                </span>
                                            )}
                                            <span 
                                                className="bg-info text-light rounded d-inline-block"
                                                style={{padding:".25rem .5rem"}}
                                            >
                                                {message.text}
                                            </span>
                                        </div>
                                    </li>
                                )
                            })}
                        </ul>
                        <TypingIndicator usersWhoAreTyping={this.props.usersWhoAreTyping}/>
                    </div>
                )}
            </div>
        )
    }
}

export default MessageList

RoomList(组件)

import React from "react"

class RoomList extends React.Component{
    render(){
        const orderedRooms = [...this.props.rooms].sort((a, b) => {
            return a.id - b.id
        })
        return(
            <div>
                { this.props.rooms.length > 0 ? (
                    <div>
                        <div className="d-flex justify-content-between text-light mb-2">
                            <h6 className="font-weight-bold">Channels</h6><i className="fa fa-gamepad"></i>
                        </div>
                        <ul style={{listStyle: "none", overflow: "scroll", overflowX: "hidden", maxHeight: "27vh"}} className="p-2">
                            {orderedRooms.map((room, index) => {
                                return(
                                    <li key={index} className="font-weight-bold mb-2">
                                        <a  
                                            onClick={() => {
                                                this.props.subscribeToRoom(room.id)
                                            }} 
                                            href="#"
                                            className={room.id === this.props.roomId ? "text-success": "text-info"}
                                            style={{textDecoration: "none"}}
                                        >
                                            <span className="mr-2">#</span>{room.name}
                                        </a>
                                    </li>
                                )
                            })}
                        </ul>               
                    </div> 
                ) : (
                    <p className="text-muted p-2">Loading...</p>
                )}
            </div>
        )
    }
}

这也是渲染ChatScreen的组件(ChannelsContainer)

import React from "react"
import UsernameForm from "./UsernameForm"
import ChatScreen from "./ChatScreen"
import { connect } from "react-redux"

class ChannelsContainer extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            chatScreen: false
        }
    }

    componentWillMount(){
        if(this.props.chatkit.chatInitialized){
            this.setState({
                chatScreen: true
            })
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.chatkit.chatInitialized){
            this.setState({
                chatScreen: true
            })
        }
    }

    render(){
        let chatStage
        if(this.state.chatScreen){
            chatStage = <ChatScreen chatUser={this.props.chatkit.chatUser}/>
        } else{
            chatStage = <UsernameForm/>
        }
        return(
            <div style={{minHeight: "90vh"}}>
                {chatStage}
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return{
        chatkit: state.chatkit
    }
}

export default connect(mapStateToProps)(ChannelsContainer)

请让我知道你们的想法。

1 个答案:

答案 0 :(得分:1)

已修复。我要做的就是将邮件的房间ID与当前房间ID进行比较。如果它们相同,那么我将更新组件状态消息字段。

        onNewMessage: (message) => {
            if(message.room.id === this.state.currentRoom.id){
                this.setState({
                    messages: [...this.state.messages, message]
                })                      
            }