Javascript 反应聊天室API。MessageList组件错误-呈现来自其他房间的消息。组件生命周期和状态

Javascript 反应聊天室API。MessageList组件错误-呈现来自其他房间的消息。组件生命周期和状态,javascript,reactjs,redux,chat,chatkit,Javascript,Reactjs,Redux,Chat,Chatkit,我在使用React构建的聊天包应用程序中遇到了一些奇怪的活动。基本上,我在不同的房间里测试两个不同的用户。当我从一个房间的用户那里发送消息时。其他用户可以看到该消息,尽管他们不在同一房间。这是正在发生的事情的截图 只有当用户至少在同一房间里呆过一次时,才会出现这种情况 我可以判断消息的创建是否正确,因为我在聊天工具包API的正确位置看到了它们。另外,如果我重新渲染组件,消息最终会出现在正确的位置。但是跨房间的消息错误仍然存在 我的印象是,它肯定与MessageList组件的状态有关。我已经确

我在使用React构建的聊天包应用程序中遇到了一些奇怪的活动。基本上,我在不同的房间里测试两个不同的用户。当我从一个房间的用户那里发送消息时。其他用户可以看到该消息,尽管他们不在同一房间。这是正在发生的事情的截图

只有当用户至少在同一房间里呆过一次时,才会出现这种情况

我可以判断消息的创建是否正确,因为我在聊天工具包API的正确位置看到了它们。另外,如果我重新渲染组件,消息最终会出现在正确的位置。但是跨房间的消息错误仍然存在

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

不用多说,下面是我的代码:

聊天屏(主应用程序)

从“React”导入React
从“@pusher/Chatkit”导入聊天工具包
从“/MessageList”导入MessageList
从“/SendMessageForm”导入SendMessageForm
从“/WhosOnlineList”导入WhosOnlineList
从“/RoomList”导入RoomList
从“/NewRoomForm”导入NewRoomForm
从“./../actions/chatkitActions”导入{getCurrentRoom}
从“react redux”导入{connect}
类ChatScreen扩展了React.Component{
建造师(道具){
超级(道具)
此.state={
信息:[],
当前房间:{},
当前用户:{},
用户重新键入:[],
可接合房间:[],
连接的房间:[],
错误:{}
}
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(){
//安装聊天工具
让tokenUrl
让instanceLocator=“somecode”
if(process.env.NODE_env==“生产”){
tokenUrl=“somenedpoint”
}否则{
令牌URL=”http://localhost:3000/api/channels/authenticate"
}
const chatManager=新建Chatkit.chatManager({
instanceLocator:instanceLocator,
userId:this.props.chatUser.name,
连接超时:120000,
tokenProvider:new Chatkit.tokenProvider({
url:tokenUrl
})
})
//启动聊天室
chatManager.connect()
.然后((当前用户)=>{
这是我的国家({
当前用户:当前用户
})
//拿到所有的房间
这个是getRooms()
//如果用户返回聊天室,请将他们引导到上次访问的房间
如果(this.props.chatkit.currentRoom.id>0){
this.subscribeToRoom(this.props.chatkit.currentRoom.id)
}
})
}
sendMessage=(文本)=>{
this.state.currentUser.sendMessage({
roomId:this.state.currentRoom.id,
文本:文本
})
}
sendTypingEvent=()=>{
this.state.currentUser
.isTypingIn({
roomId:this.state.currentRoom.id
})
.catch((错误)=>{
这是我的国家({
错误:错误
})
})
}
getRooms=()=>{
this.state.currentUser.getJoinableRooms()
.然后((可接合房间)=>{
这是我的国家({
可接合房间:可接合房间,
joinedRooms:this.state.currentUser.rooms
})
})
.catch((错误)=>{
这是我的国家({
错误:{错误:“无法检索房间”}
})
})
}
subscribeToRoom=(房间ID)=>{
这是我的国家({
信息:[]
})
this.state.currentUser.subscribeToRoom({
室友:室友,
挂钩:{
onNewMessage:(消息)=>{
这是我的国家({
消息:[…this.state.messages,message]
})
},
onUserStartedTyping:(当前用户)=>{
这是我的国家({
usersWhoAreTyping:[…this.state.usersWhoAreTyping,currentUser.name]
})
},
ONUSERSTOPPED键入:(当前用户)=>{
这是我的国家({
usersWhoAreTyping:this.state.usersWhoAreTyping.filter((用户)=>{
返回用户!==currentUser.name
})
})
},
onUserCameOnline:()=>this.forceUpdate(),
onUserWentOffline:()=>this.forceUpdate(),
onUserJoined:()=>this.forceUpdate()
}           
})
.然后((当前房间)=>{
这是我的国家({
当前房间:当前房间
})
这个是getRooms()
//将currentRoom存储在redux状态
这个.props.getCurrentRoom(currentRoom)
})
.catch((错误)=>{
这是我的国家({
错误:错误
})
})
}
createRoom=(roomName)=>{
this.state.currentUser.createRoom({
姓名:roomName
})
.然后((新房间)=>{
this.subscribeToRoom(newRoom.id)
})
.catch((错误)=>{
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)
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
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>
        )
    }
}
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)
onNewMessage: (message) => {
  if(message.room.id === this.state.currentRoom.id){
    this.setState({
      messages: [...this.state.messages, message]
    })                      
  }
}