簡體   English   中英

為什么 state 在反應中會自行改變?

[英]Why is the state changing by itself in react?

我正在嘗試做的事情:我有以下反應組件,它是聊天框,它在單擊按鈕時切換到查看。 聊天框將根據 state 'open' 中的值打開或關閉。 當用戶點擊聊天按鈕時,function 'toggleChat' 運行,它只是在真假之間切換'open' state 的值。

問題:現在的問題是,當收到新消息時,如果聊天框沒有“打開”,我會嘗試保持未讀消息的計數。 但它失敗了。 在我看來它應該可以工作,但“開放”state 有時並不是我所期望的。 有時,即使打開了聊天框,里面打開的 state 也是“假”。

縮小代碼

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);
        }
    }

整個代碼

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>
      </>
    );
}

嘗試將依賴項添加到 function 中使用的 useEffect。

還要添加const [msgArr, setMsgArr] = useState([]); 如果它不存在。

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]);

如果依賴項發生更改,則更新事件處理程序的取消注冊。 (基於評論討論和回答https://stackoverflow.com/a/67073527/8915198

已經在評論中給出了對closures的解釋,但您的工作解決方案應該如下所示:-

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]);

基本上,我認為清理步驟是必要的,就像您對重大事件范例所做的那樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM