I am new for development.I have created a chat application using socket io.I have the useEffect change only when socket changes, but I have setMessage in the body of useEffect() meaning update the message and I put the socket as dependency.But this doesn't work and it re-runs infinitum.
import React from 'react';
import ScrollToBottom from 'react-scroll-to-bottom';
import { useState,useEffect } from 'react';
function Chat({socket,username,room}) {
const [currentMessage, setCurrentMessage] = useState("");
const [messageList, setMessageList] = useState([]);
const sendMessage = async () => {
if (currentMessage !== "") {
const messageData = {
room: room,
author: username,
message: currentMessage,
time: new Date(Date.now()).getHours()
':'
new Date(Date.now()).getMinutes()
};
await socket.emit("send_message", messageData);
sendMessage((list)=>[...list,messageData]);
}
};
useEffect(() => {
socket.on("receive_message", (data) => {
setMessageList((list) => [...list, data]);
setCurrentMessage("");
});
}, [socket]);
return (
<div className='chat-window'>
<div className='chat-header'>
<p>Live Chat</p>
</div>
<div className='chat-body'>
<ScrollToBottom className='message-container'>
{messageList.map((messageContent)=>{
return (
<div className='message' id={username===messageContent.author?"you":"other"}>
<h1>{messageContent.message}</h1>
</div>
);
})}
</ScrollToBottom>
</div>
<div className='chat-footer'>
<input type="text" value={currentMessage}placeholder='Hey..' onChange={(event) => {
setCurrentMessage(event.target.value);
}}
onKeyPress={(event)=>{
event.key==='Enter'&&sendMessage();
}}/>
<button onClick={sendMessage}>►</button>
</div>
</div>
);
}
export default Chat;
How to stop rendering the useEffect function?
CodePudding user response:
Why you are passing receive_message socket listener to the useEffect? did you tried without using useEffect
socket.on("receive_message", (data) => {
setMessageList((list) => [...list, data]);
setCurrentMessage("");
});
CodePudding user response:
There is a recursion happening in sendMessage Function, which is causing an infinite loop.
Removing the below line may fix the issue
sendMessage((list)=>[...list,messageData]);
Also, don't forget to remove the socket from the dependency array.
CodePudding user response:
Actually i have ran into this problem recently, however there's some options that you can try:
- Add listener in the file where socket is initialized
Instead of passing socket into child component, you should only pass the data to the child component
example index.js
:
const [messageList, setMessageList] = useState([]);
// Do not use any deps here
useEffect(() => {
let socket = init() // or something
socket.on("receive_message", (data) => {
setMessageList((list) => [...list, data]);
setCurrentMessage("");
});
}, [])
return (
<ChildComponent messageList={messageList} />
)
With this solution, the useEffect
should only render once, however if you wanted to pass down the socket, you can use useRef
instead of useEffect
since it does not re-render and works just like a normal variable.
- Use Custom Window Dispatch Event Hook
I understand that you are trying to have clean code here, so maybe the alternatives is using EventTarget.dispatchEvent() browser API, this method is the one i usually going with.
With this you just need to create custom hooks to emit/listen the data, here's mine:
const [data, setData] = useState<T | null>(null);
useEffect(() => {
const handler = ((event: CustomEvent<CustomEventDetail>) => {
if (event.detail.emitId === id) return;
setData(event.detail.data);
}) as EventListener;
window.addEventListener(eventName, handler);
return () => {
window.removeEventListener(eventName, handler);
};
}, [eventName, id]);
const emit = (data: T) => {
const customEvent = new CustomEvent<CustomEventDetail>(eventName, {
detail: {
emitId: id,
data: data,
},
});
setData(data);
window.dispatchEvent(customEvent);
};
- Create Validator in the
useEffect
Maybe the last thing you wanted to try is using a validator
useEffect(() => {
socket.on("receive_message", (data) => {
if (currentMessage !== data.message) {
setMessageList((list) => [...list, data]);
setCurrentMessage("");
}
});
}, [socket]);
however, i don't know if it would works on don`t
Also for notes, don't forget to use return on the useEffect
, so the function inside it wouldn't called multiple times because of component re-rendering, such as
const handler = () => {}
socket.on("receive_message", handler);
return () => {
socket.off("receive_message", handler);
}
Hope my comments give you a solution, let me know if anything above is works for you!
CodePudding user response:
I think you don't have to pass socket as a dependency.
Test this snippet and let me know if it's working.
useEffect(() => {
socket.on("receive_message", (data) => {
setMessageList((list) => [...list, data]);
setCurrentMessage("");
});
return () => {
socket.off('receive_message');
}
}, []);