Reputation:
I'm building a chat application in react. Now the problem that I ran into, is when I click on a button, the handleMsgSend button runs, and dispatches a 'newMsg' event to the other client. On receiving this event, I expect the msgArr state to update with the newly received msg, but no. All in it is the new msg, and nothing else. Why is that happenning? I feel like I skipped technical stuffs while learning react.
export default function Chat (props) {
const uuid = props.uuid;
const classes = useStyles();
const [open, setOpen] = useState(false);
const [activeStatus, setActiveStatus] = useState('Offline');
const [unreadMsgs, setUnreadMsgs] = useState(0);
const [msgArr, setMsgArr] = useState([]);
const chatBtnRef = useRef();
const chatBoxRef = useRef();
const msgInputRef = useRef();
useEffect(() => {
socket.emit('join', uuid);
socket.emit('isOnline', uuid);
socket.on('newMsg', msg => {
setMsgArr([ ...msgArr, { type: 'received', msg }]);
console.log(msgArr);
if(!open) setUnreadMsgs(unreadMsgs + 1);
chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
});
socket.on('isOnline', () => {
setActiveStatus('Online');
socket.emit('isOnline');
});
return () => {
console.log('removed');
socket.off('newMsg');
socket.off('Online');
}
}, []);
const handleMsgSend = e => {
e.preventDefault();
let msg = msgInputRef.current.value;
setMsgArr([ ...msgArr, { type: 'sent', msg }]);
e.currentTarget.reset();
socket.emit('newMsg', {uuid, msg});
chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
}
const toggleChat = () => {
setUnreadMsgs(0);
if(open) setOpen(false);
else setOpen(true);
}
How I am rendering the msgArr:
<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>
Upvotes: 0
Views: 71
Reputation: 281716
your socket listener is attached inside a useEffect
which is executed only on initial render, so the value of msgArr which you see inside it will always refer to the initial value defined in state due to closure.
Now since msgArr is used from closure in socket listener, you will always be refering to the initial state and not the updated one even when you receive new messages.
To solve this, you must use callback
approach to update state
socket.on('newMsg', msg => {
setMsgArr(prevMsgArr => [ ...prevMsgArr, { type: 'received', msg }]);
if(!open) setUnreadMsgs(unreadMsgs + 1);
chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
});
Once you do that you would be able to see the updated value of state in the next render cycle and consequently be able to render the updated content based on the array
P.S. Please note that console.log(msgArr);
immediately after setMsgArr
will not give you the updated value since state updates are async and affected by closure. Refer this post for more insight on this.
Upvotes: 1