Reactjs button bug – parent receives new data – child component doesn’t update properly

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.

enter image description here

+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))