reactjs bug — I have a node api and react shell and I am running into some bugs I am having difficulty in figuring out how to fix.
+blocking and favoriting in chat buggy chat – pane and about part (props changing issue)
^handling the props and child refreshes from parent to child?
user tbl holds 2 lists blocked, fav — if you block a person that was in your fav, they are auto removed from the fav list. If favourited they are removed from block list. userid is only in ONE list or none.
Data comes back to the parent and informs the child components that the relationship has changed – but its almost like I have to click twice… as if the props havent been updated properly?
parent chat component — pushes data to input and about pane
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Grid } from "@mui/material";
import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer';
import ChatThread from './ChatThread';
import ChatAbout from './ChatAbout';
import ChatPane from './ChatPane';
import { fetchChats, fetchChatsById, createNewChatBetweenCurrentAndOthers, markAllMessagesInChatAsSeen /*, createNewChat, createNewChatBetweenUsers, fetchChatsMessageToId, markAMessageInChatAsSeen*/ } from '../../../actions/chatsAction';
import { buildChatArray, getAbout } from './ChatHelper';
import { fetchResources } from '../../../actions/resourcesAction';
import { sortArrayByDateAsc, sortArrayByDateDesc } from '../Utility/Utility';
import { getToken, getUserDetails, isLogged } from '../UserFunctions/UserFunctions';
import { scrollToBottom } from './CommonChatFunctions.js';
//import { initTracking, logPageView } from '../_SharedGlobalComponents/TrackingFunctions/_groupTracking';
//import { audioHandler } from '../_SharedGlobalComponents/AudioHandler/AudioHandler';
import './ChatBox.scss';
class ChatBox extends Component {
constructor(props, context) {
super(props, context);
this.state = {
hasNewMessage: false,
currentChatId: -1,
chatConversation: [],
messageList:[],
aboutUser: {},
messageDiffCount: 0,
oldChatConversationLength: 0
};
this.paneRef = React.createRef();
this.submitChatFormHandler = this.submitChatFormHandler.bind(this);
this.markAsRead = this.markAsRead.bind(this);
this.getChatConversation = this.getChatConversation.bind(this);
this.friendshipChange = this.friendshipChange.bind(this);
this._isMounted = false;
}
componentDidMount(){
this._isMounted = true;
//console.log("chat room time");
//initTracking();
//logPageView();
if(isLogged()){
let that = this;
//console.log("is loggedin");
this.getResources(function(res){
//console.log("resourced obtained", res);
that.getMessageThreadList(function(res){
//console.log("getMessageThreadList");
//console.log("-------------xxxxxxxxxx res", res);
that.snapToChatThread();
});
that.pollingUpdates();
});
}
}
snapToChatThread(){
//console.log("snap chat");
const getQueryParams = () =>
this.props.location.search
.replace('?', '')
.split('&')
.reduce((r,e) => {
r[e.split('=')[0]] = decodeURIComponent(e.split('=')[1]);
return r;
}, {});
//console.log("getQueryParams", getQueryParams());
if(Object.keys(getQueryParams()).length !== 0 && getQueryParams().id){
//console.log("get chat")
this.getChatConversation(getQueryParams().id);
}
}
getResources(callback){
let that = this;
let data = {};
this.props.fetchResources(data, function(resp) {
if(resp){
let resources = resp.data;
let countryOptions = [];
for(let i = 0; i < resources.countries.length; i++){
let obj = {
"label": resources.countries[i].name,
"value": resources.countries[i].code
}
countryOptions.push(obj);
}
resources["countries"] = countryOptions;
that.setState({
resources: resources,
isResourcesReady: true
});
callback(resources);
}
});
}
pollingUpdates(){
//if NEW notification messages -- start checking current chat
//call get chat conversation id every 20 seconds?
let that = this;
let waitToCheck = 45000;
//console.log("pollingUpdates----------")
//console.log("-1-this._isMounted", this._isMounted)
this.timer = setTimeout(() => {
//console.log("-----------timer", this.timer)
//console.log("-2-this._isMounted", this._isMounted)
this.intervalTimer = setInterval(() => {
//console.log("---------this.intervalTimer", this.intervalTimer)
//console.log("-3-this._isMounted", this._isMounted)
//this.getMessageThreadList(function(res){console.log("res",res)})
if(that._isMounted && that.state.currentChatId !== -1){
//this.getChatConversation(that.state.currentChatId);
that.replenish(that.state.currentChatId);
} else{
that.clearClocks();
}
}, waitToCheck);
}, waitToCheck);
}
clearClocks(){
console.log("clearClocks")
if (this.timer) {
clearTimeout(this.timer);
this.timer = 0;
}
if (this.intervalTimer) {
clearInterval(this.intervalTimer);
this.intervalTimer = 0;
}
}
componentWillUnmount() {
console.log("---unmount")
this._isMounted = false;
this.setState({
currentChatId : -1
})
this.clearClocks();
}
replenish(currentChatId) {
//console.log("--------------------replenish");
this.getChatConversation(currentChatId);//refresh current chat pane
this.getMessageThreadList(function(res){console.log("res",res)});//refresh side pane
}
submitChatFormHandler(data){
let that = this;
//only send text to the server if there is text
if(data && data.text.length>0){
this.props.createNewChatBetweenCurrentAndOthers(this.state.currentChatId, data, getToken(), function(resp){
that.replenish(that.state.currentChatId);
});
}
}
buildChatMessageObj(data){
let chatArray = buildChatArray(data, getUserDetails().id);
this._isMounted && this.setState({ chatConversation: chatArray, oldChatConversationLength: chatArray.length });
this.setAboutUser();
let lastChatItem = chatArray[chatArray.length - 1];
let newMessageSinceLoad = this.state.messageDiffCount > 0 && !this.state.initialChatLoad;
let isLastMessageByLoggedInUser = lastChatItem.user_id === getUserDetails().id;
if(newMessageSinceLoad && !isLastMessageByLoggedInUser){
//audioHandler("new-chat-message");
}
//if initial load is true -- scroll to bottom
//if message diffcount has changed and its NOT initial load - a new message has come in - scroll to bottom
if(
(this.state.initialChatLoad) ||
(newMessageSinceLoad)
) {
this.timer = setTimeout(() => {
scrollToBottom(this.paneRef.current);
//this.scrollToBottom();//scroll to the bottom after getting all the chat convo
}, 200);
}
}
getChatConversation(id){
let that = this;
this._isMounted && this.setState({ currentChatId: id });
if(this._isMounted){
console.log("this._isMounted", this._isMounted)
console.log("id", id);
window.history.pushState("", "", 'chat?id='+id);//updates the url with the current chat id
this.props.fetchChatsById(id, getToken(), function(resp){
if(resp){
if(resp.data){
that.setState({
chatConversationLength: resp.data.messageThreads.length,
messageDiffCount: resp.data.messageThreads.length - that.state.oldChatConversationLength,
initialChatLoad: that.state.oldChatConversationLength === 0? true:false
})
that.buildChatMessageObj(sortArrayByDateDesc(resp.data.messageThreads, "created_at"));
}
}
});
}
}
buildMessageThreadListObj(data){
//console.log("buildMessageThreadListObj", data)
this._isMounted && this.setState({ messageList: sortArrayByDateAsc(data, "lastSeen") });
}
getMessageThreadList(callback){
let that = this;
//console.log("getMessageThreadList");
//fetch chats for this user
this.props.fetchChats(getToken(), function(resp){
//console.log("resp", resp);
if(resp){
if(resp.data){
//console.log("resp", resp);
that.buildMessageThreadListObj(resp.data.chatThreads);
that.timer = setTimeout(() => {
callback(resp.data.chatThreads);
}, 1);
}
}
});
}
markAsRead(){
//console.log("mark ALL messages as seen");
this.props.markAllMessagesInChatAsSeen(this.state.currentChatId, {}, getToken(), function(resp){
//console.log("--markAllMessagesInChatAsSeen--resp", resp);
});
}
setAboutUser(){
let messageThread = this.state.messageList.filter(obj => {
return obj.id.toString() === this.state.currentChatId.toString();
})[0];
let aboutUser = getAbout(messageThread, this.state.resources);
this._isMounted && this.setState({ aboutUser: aboutUser });
}
friendshipChange(label, value) {
console.log("friendshipChange------",label, value);
this.replenish(this.state.currentChatId);
}
render() {
return (
<div className="Page">
<div className="common-block chat-container">
{!isLogged() &&
<div className="shield"></div>
}
<Grid container spacing={0}>
<Grid item xs={12} sm={3}>
<ChatThread
messageList={this.state.messageList}
currentChatId={this.state.currentChatId}
getChatConversation={this.getChatConversation}
/>
</Grid>
{this.state.currentChatId === -1 ?
(
<Grid item xs={12} sm={9}>
<div className="unselected-chat-message-container">
<div className="unselected-chat-message-wrapper">
<QuestionAnswerIcon className="unselected-chat-icon" />
<h2>Select a Conversation</h2>
<p>Try selecting a conversation</p>
</div>
</div>
</Grid>
):
(
<>
<Grid item xs={12} sm={6}>
<ChatPane
chatConversation={this.state.chatConversation}
submitChatFormHandler={this.submitChatFormHandler}
markAsRead={this.markAsRead}
paneRef={this.paneRef}
aboutUser={this.state.aboutUser}
friendshipChange={this.friendshipChange}
/>
</Grid>
<Grid item xs={12} sm={3}>
<ChatAbout
aboutUser={this.state.aboutUser}
friendshipChange={this.friendshipChange}
/>
</Grid>
</>
)
}
</Grid>
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
chatsData: state.chats,
resourcesData: state.resources
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({fetchChats, fetchChatsById, createNewChatBetweenCurrentAndOthers, markAllMessagesInChatAsSeen, fetchResources /*fetchChatsMessageToId, createNewChat, createNewChatBetweenUsers, markAMessageInChatAsSeen*/}, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ChatBox))
chat input pane
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Grid } from '@mui/material';
import NotificationAvatar from '../NotificationHandler/NotificationAvatar';
import ChatInput from './ChatInput';
import parse from 'html-react-parser';
import { getUserDetails } from '../UserFunctions/UserFunctions';
import { getToken } from '../../_globals/UserFunctions/UserFunctions';
import { readUsers, comparisonUsers, favUsers, blockUsers, viewedUsers } from '../../../actions/userAction';
import moment from 'moment';
import './ChatPane.scss';
class ChatPane extends Component {
constructor(props, context) {
super(props, context);
this.state = {
isReady: false,
//userId: this.props.match.params.userId
};
this._isMounted = false;
this.blockHandler = this.blockHandler.bind(this);
}
//aboutUser
componentDidUpdate(prevProps) {
console.log("prevProps", prevProps)
if (prevProps.aboutUser !== this.props.aboutUser) {
console.log("CHANGED -- aboutUser", this.props.aboutUser)
this.setState({
isReady: false
});
let that = this;
this.timer = setTimeout(() => {
that.setState({
isReady: true
});
}, 1);
}
}
componentDidMount(){
this.setState({
isReady: true
});
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
clearInterval(this.timer);
clearInterval(this.intervalTimer);
}
showNewMessage(item){
//check if YOU made the NEW message --- and if so don't show it your end as a new message
if(!item.seen && (getUserDetails().id !== item.user_id)) {
return true;
}
return false;
}
blockHandler(state){
//console.log("click Block");
//let userId = this.props.match.params.userId;
let that = this;
let data = {"id":this.props.aboutUser.id}
this.props.blockUsers(data, getToken(), function(resp) {
if(resp){
console.log("resp-blockUsers", resp.data);
//that.getComparisons();
that.props.friendshipChange("blockChange", false);
}
});
}
render() {
console.log("CHATPANE-------------this.props.aboutUser", this.props.aboutUser);
return (
<div className="chat-message-container">
<div
className="chat-pane"
ref={this.props.paneRef}
>
{
this.props.chatConversation.map((item, i) => {
//console.log("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxitem", item)
return(
<div key={i} className="chat-row">
{this.showNewMessage(item) && (item.hasNewFlagId === item.i) &&
<div className="new-message"><div className="new-message-text">New Messages</div></div>
}
<div className="chat-wrapper">
<Grid container spacing={1}>
<Grid item xs={12} sm={2} md={1}>
<NotificationAvatar id={item.user_id} name={item.name} image={item.image}/>
</Grid>
<Grid item xs={12} sm={10} md={11}>
<div><h4>{item.name}</h4> {moment(item.date).format('MMM Do YY, h:mm a')}</div>
<div>{parse(item.subject)}</div>
</Grid>
</Grid>
</div>
</div>
)
})
}
{this.state.isReady && (this.props.aboutUser?.relationState?.isLoggedInUserBlocked || this.props.aboutUser?.relationState?.isParticipantUserBlocked) &&
<div className="chat-row">
<div className="chat-wrapper">
<p>Only visible to you</p>
<div className="system-block-message">
{/*participant has block -- */}
{this.props.aboutUser?.relationState?.isParticipantUserBlocked && !this.props.aboutUser?.relationState?.isLoggedInUserBlocked &&
<p>{this.props.aboutUser.name} has blocked you, you can no longer contact them.</p>
}
{/*loggedin has block -- */}
{this.props.aboutUser?.relationState?.isLoggedInUserBlocked && !this.props.aboutUser?.relationState?.isParticipantUserBlocked &&
<p>You have blocked {this.props.aboutUser.name}. If you wish to send a message, you will need to <a onClick={()=>this.blockHandler()}>unblock</a>.</p>
}
{/*both block -- */}
{this.props.aboutUser?.relationState?.isLoggedInUserBlocked && this.props.aboutUser?.relationState?.isParticipantUserBlocked &&
<p>Communication has been disabled. {this.props.aboutUser.name} can no longer contact you. If you wish to send a message, you will need to <a onClick={()=>this.blockHandler()}>unblock</a>.</p>
}
</div>
</div>
</div>
}
</div>
<div className="chat-input-wrapper" onClick={this.props.markAsRead}>
{this.state.isReady &&
<ChatInput
aboutUser={this.props.aboutUser}
submitHandler={this.props.submitChatFormHandler}
/>
}
</div>
</div>
)
}
}
function mapStateToProps(state) {
return {
userData: state.user,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({blockUsers}, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ChatPane))
toggle button usage in the about pane
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import NotificationAvatar from '../NotificationHandler/NotificationAvatar';
import SimpleTable from '../SimpleTable/SimpleTable';
import ToggleButton from '../../_globals/ToggleButton/ToggleButton';
import { getToken } from '../../_globals/UserFunctions/UserFunctions';
import { readUsers, comparisonUsers, favUsers, blockUsers, viewedUsers } from '../../../actions/userAction';
import './ChatAbout.scss';
class ChatAbout extends Component {
constructor(props, context) {
super(props, context);
this.state = {
isReady: false,
//userId: this.props.match.params.userId
};
this._isMounted = false;
this.favHandler = this.favHandler.bind(this);
this.blockHandler = this.blockHandler.bind(this);
}
componentDidMount(){
this.setState({
isReady: true
});
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
clearInterval(this.timer);
}
componentDidUpdate(prevProps) {
console.log("prevProps", prevProps)
if (prevProps.aboutUser !== this.props.aboutUser) {
console.log("CHANGED -- aboutUser", this.props.aboutUser)
this.setState({
isReady: false
});
let that = this;
this.timer = setTimeout(() => {
that.setState({
isReady: true
});
}, 1);
}
}
favHandler(){
console.log("favHandler");
let that = this;
let data = {id: this.props.aboutUser?.id};
this.props.favUsers(data, getToken(), function(resp) {
if(resp){
console.log("resp", resp);
//changed relationship -- show change
// that.props.friendshipChange("favChange", false);
//that.timer = setTimeout(() => {
that.props.friendshipChange("favChange", false);
//}, 1);
}
});
}
blockHandler(){
console.log("blockHandler");
//console.log("click Block");
//let userId = this.props.match.params.userId;
let that = this;
let data = {"id":this.props.aboutUser?.id}
this.props.blockUsers(data, getToken(), function(resp) {
if(resp){
console.log("resp-blockUsers", resp.data);
//that.getComparisons();
// that.props.friendshipChange("blockChange", false);
//that.timer = setTimeout(() => {
that.props.friendshipChange("blockChange", false);
//}, 1);
}
});
}
render() {
console.log("xxxxxxxxxxxxx this.props.aboutUser", this.props.aboutUser);
return (
<div className="user-container">
<h3>About</h3>
{this.props.aboutUser && Object.keys(this.props.aboutUser).length > 0 &&
(
<>
<div className="user-info">
<NotificationAvatar id={this.props.aboutUser.id} image={this.props.aboutUser.image} name={this.props.aboutUser.name} />
<h3><a href={"/users/"+this.props.aboutUser.id}>{this.props.aboutUser.name}</a></h3>
<h4>{this.props.aboutUser.memberType}</h4>
</div>
<SimpleTable type="plain" rows={this.props.aboutUser.data.rows} />
</>
)
}
{this.state.isReady && this.props.aboutUser && Object.keys(this.props.aboutUser).length > 0 &&
(
<>
<p>isLoggedInUserFavorite {this.props.aboutUser.relationState.isLoggedInUserFavorite? "True":"False"}</p>
<p>isLoggedInUserBlocked {this.props.aboutUser.relationState.isLoggedInUserBlocked? "True":"False"}</p>
<p>isParticipantUserFavorite {this.props.aboutUser.relationState.isParticipantUserFavorite? "True":"False"}</p>
<p>isParticipantUserBlocked {this.props.aboutUser.relationState.isParticipantUserBlocked? "True":"False"}</p>
<div className="user-relations">
<ToggleButton
label1="Unfavorite"
label2="Favorite"
toggle={this.props.aboutUser.relationState.isLoggedInUserFavorite}
onClick={this.favHandler}
/>
<ToggleButton
label1="Unblock"
label2="Block"
toggle={this.props.aboutUser.relationState.isLoggedInUserBlocked}
onClick={this.blockHandler}
/>
</div>
{/*
<ToggleButton
label1="Unblock"
label2="Block"
//disabled={(this.state.user?.id === getUserDetails().id)}
toggle={this.props.aboutUser.blockedDetails.isParticipantUserBlocked? true:false}
onClick={(toggled)=> this.blockUser(toggled)}
/>
*/}
</>
)
}
</div>
)
}
}
function mapStateToProps(state) {
return {
userData: state.user,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({favUsers, blockUsers}, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ChatAbout))
code for the toggle button
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Button from '@mui/material/Button';
//import TooltipInfoHandler from '../TooltipInfoHandler/TooltipInfoHandler';
//import { getToken, redirectUrl } from '../UserFunctions/UserFunctions';
//import { createNewChat, createNewChatBetweenCurrentAndOthers } from '../../../actions/chatsAction';
//import './ConnectButton.scss';
class ToggleButton extends Component {
constructor(props, context) {
super(props, context);
this.state = {
//labelTrue: this.props.labelTrue,
//labelFalse: this.props.labelFalse,
toggle: this.props.toggle,
label: this.props.toggle? this.props.label1:this.props.label2,
};
this.clicked = this.clicked.bind(this);
}
componentDidMount(){
}
componentDidUpdate(prevProps) {
let that = this;
if (prevProps.toggle !== this.props.toggle) {
that.setState({
toggle: this.props.toggle,
label: this.props.toggle? this.props.label1 : this.props.label2
});
}
}
componentWillUnmount(){
//this._isMounted = false;
clearInterval(this.timer);
}
clicked(){
//console.log("clicked on but", this.props);
let that = this;
that.setState({
toggle: !this.state.toggle,
label: !this.state.toggle? this.props.label1 : this.props.label2
});
this.timer = setTimeout(() => {
//console.log("that.state.toggle", that.state.toggle);
//console.log("that.state.label", that.state.label);
that.props.onClick(that.state.toggle);
}, 1);
}
render() {
{
//console.log("xxxxxxxxxxx----------this.state.toggle", typeof this.state.toggle)
}
return (
<div className="toggle-wrap">
<Button variant="contained" disabled={this.props.disabled} onClick={this.clicked} color={this.state.toggle? "secondary":"primary"}>
{this.state.label}
</Button>
{/*<TooltipInfoHandler helper={this.props.toolTipText} />*/}
</div>
);
}
}
function mapStateToProps(state) {
return {};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({}, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ToggleButton))