4

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

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

越野车聊天

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

更正聊天

我的印象是它肯定与 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)

消息列表(组件)

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)

请让我知道你们的想法。

4

1 回答 1

2

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

onNewMessage: (message) => {
  if(message.room.id === this.state.currentRoom.id){
    this.setState({
      messages: [...this.state.messages, message]
    })                      
  }
}
于 2018-10-18T07:05:25.540 回答