[英]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.