简体   繁体   中英

Why is the state changing by itself in react?

What I'm Trying to do : I have the following react component, which is chat box, which toggles to view on clicking a button. The chatBox will be opened or closed according the value in the state 'open'. When the user clicks on the chat button, the function 'toggleChat' is run, which just toggles the value of 'open' state between true and false.

Problem : Now the problem is, when a new message is received, I am trying to keep the count of unread messages, if the chatBox isn't 'opened'. But it fails. In my opinion it should work, but the 'open' state is not what I expect it to be sometimes. Sometimes, even though the chatBox is opened, the open state inside is 'false'.

Minified Code

export default function Chat (props) { 
    const [open, setOpen] = useState(false);
    const [unreadMsgs, setUnreadMsgs] = useState(0);
    useEffect(() => {
        socket.emit('join', uuid);
        socket.on('newMsg', msg => {
            setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
            if(!open) setUnreadMsgs(prevCount => prevCount + 1);
        });
    }, []);

    const toggleChat = () => {
        if(open) setOpen(false);
        else {
            setOpen(true);
            setUnreadMsgs(0);
        }
    }

Entire code

import { io } from 'socket.io-client';
import React, {useState, useRef, useEffect} from 'react';
import Menu from '@material-ui/core/Menu';
import MailIcon from '@material-ui/icons/Mail';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import { makeStyles } from '@material-ui/core/styles';
import Badge from '@material-ui/core/Badge';
import Tooltip from '@material-ui/core/Tooltip';
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';

const socket = io('http://127.1.1.1:4000');

const useStyles = makeStyles({
    paper : {
        height : 300,
    },
    list : {
        height : '100%',
        boxSizing : 'border-box',
    },
    chatContainer : {
        position : 'relative',
        height : '95%',
        width : 300,
    },
    chatBox : {
        height : '82%',
        position : 'absolute',
        top : '8%',
        width : '100%',
        overflowY : 'auto',
    },
    msgForm : {
        width : '100%',
        padding : 10,
        position : 'absolute',
        bottom : 0,
        height : '6%',
        textAlign : 'center',
    },
    anchor : {
        top : 7,
    },
    badge : {
        background : '#007eff',
    },
});

export default function Chat (props) { 
    const uuid = props.uuid;
    const classes = useStyles();
    const [open, setOpen] = useState(false);
    const [activeStatus, setActiveStatus] = useState('Offline');
    const [msgArr, setMsgArr] = useState([]);
    const chatBtnRef = useRef();
    const chatBoxRef = useRef();
    const msgInputRef = useRef();
    //working on showing count of unread messages
    const [unreadMsgs, setUnreadMsgs] = useState(0);
    useEffect(() => {
        socket.emit('join', uuid);
        socket.on('newMsg', msg => {
            setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
            setTimeout(() => {
                chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight; 
            }, 50);
            console.log(open);
            if(!open) setUnreadMsgs(prevCount => prevCount + 1);
        });
        if(props.isReceiver) {
            socket.emit('isReceiverOnline', uuid);
            socket.on('isSenderOnline', () => {
                setActiveStatus('Online');
            });
        } else {
            socket.emit('isSenderOnline', uuid);
            socket.on('isReceiverOnline', () => {
                setActiveStatus('Online');
                socket.emit('isSenderOnline', uuid);
            });
        }
        socket.on("isOffline", () => {
            setActiveStatus('Offline');
        });
        return () => {
            socket.off('isOffline');
            socket.off('newMsg');
            socket.off('isOnline');
        }
    }, []);

    const handleMsgSend = e => {
        e.preventDefault();
        let msg = msgInputRef.current.value;
        setMsgArr([ ...msgArr, { type: 'sent', msg }]);
        e.currentTarget.reset();
        socket.emit('newMsg', {uuid,  msg});
        setTimeout(() => chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight, 50);
    }

    const toggleChat = () => {
        if(open) setOpen(false);
        else {
            setOpen(true);
            setUnreadMsgs(0);
        }
    }
    return (
        <>
        <Tooltip title={ `${activeStatus}` }>
            <IconButton>
                <FiberManualRecordIcon style={{ height : 14, width : 14, fill : (activeStatus == "Offline")? '#00000057' : 'rgb(136 210 130)'}}/>
            </IconButton>
        </Tooltip>
        <Tooltip title={ `${unreadMsgs}  unread messages` }>
                <IconButton ref={chatBtnRef} onClick={ toggleChat }>
                    <MailIcon style={{ fill:'white' }}/>
                </IconButton>      
        </Tooltip>
        <Menu
            classes={{ paper : classes.paper, list : classes.list}}
            anchorEl={chatBtnRef.current}
            keepMounted
            open={ open }
        >   
            <div style={{ position : 'relative', zIndex : '1', height : '5%', textAlign:'right'}}>
                <IconButton onClick={ toggleChat }>
                    <CloseIcon />
                </IconButton>
            </div>
            <div className={classes.chatContainer}>
                <div ref={ chatBoxRef } className={ classes.chatBox }>
                    {
                        msgArr.map((msgObj, index) => {
                            return (
                                <div key={index} className={`msg-container ${(msgObj.type == 'sent')? 'myMsg' : 'hisMsg'}`}>
                                    <span className='msg'>
                                        { msgObj.msg }
                                    </span>
                                </div>
                            )
                        })
                    }                     
                </div>
                <form className={ classes.msgForm } onSubmit={ handleMsgSend }>
                    <input
                        style ={{
                            padding : 3,
                            fontSize : 14,
                            borderRadius : 3,
                            width : 250
                        }}
                        ref={msgInputRef} type="text" 
                        className={classes.msgInput} 
                        placeholder="Type your Msg here."/>
                </form>
            </div>
      </Menu>
      </>
    );
}

Try adding the dependencies to useEffect that are used in the function.

Also add const [msgArr, setMsgArr] = useState([]); if it is not there.

useEffect(() => {
  socket.emit('join', uuid);
}, []); // you can pass socket here as it's not going to change

useEffect(() => {
  socket.on("newMsg", (msg) => {
    setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
    if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
  });

  return () => socket.removeAllListeners("newMsg"); // remove earlier registered handlers
}, [open]);

Updated the De-registration of the event handler if a dependency changes. (based on comment discussion and answer https://stackoverflow.com/a/67073527/8915198 )

Already given the explanation for closures in the comments but the working solution for you should look something like below:-

useEffect(() => {
  socket.emit('join', uuid);
}, []);

useEffect(() => {

  function getNewMsg(msg){
    setMsgArr((prevMsgArr) => [...prevMsgArr, { type: "received", msg }]);
    if (!open) setUnreadMsgs((prevCount) => prevCount + 1);
  }

  socket.on("newMsg",getNewMsg)

  return ()=>socket.removeListener("newMsg",getNewMsg);
}, [open]);

Basically the cleanup step I think is necessary as you do with major event paradigms .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM