Every time I send a message, map()
only shows my new message when I type something in the input (re-render).
The listener works fine, it displays new messages in the console immediately after sending a new message, curiously, the messages
state also updates when I look in React Developers Tools,
But useEffect[messages]
does not trigger on a new message, and it only displays new messages after I type something in the input.
Here is my component with comments:
import { useState, useEffect, useRef } from "react";
const ChatWindowChannel = ({ window, chat }) => {
const [messages, setMessages] = useState([]);
const [message, setMessage] = useState("");
const loaded = useRef(null);
const messagesEndRef = useRef(null);
const channelMessagesHandler = async () => {
const channelMessagesListener = await chat.loadMessagesOfChannel(window);
channelMessagesListener.on((msgs) => {
console.log(msgs); // Here shows new messages after I click `send`
// Works correct.
setMessages(msgs); // In React Developers Tools the state `messages` are update if I click `send`
// Works correct.
console.log(messages); // Shows always empty array[]
// Dont works correct
});
};
async function send() {
await chat.sendMessageToChannel(window, message, {
action: "join",
alias: chat.gun.user().alias,
pubKey: chat.gun.user().is.pub,
name: "grizzly.crypto",
});
setMessage("");
}
useEffect(() => {
// Shows only once. It shold every time on new message
console.log("Messages changed");
}, [messages]);
useEffect(() => {
loaded.current !== window.key && channelMessagesHandler();
loaded.current = window.key;
}, [window]);
return (
<div>
<div className="ChatWindow">
<h2>
Channel {window.name} - {window.isPrivate ? "Private" : "Public"}
</h2>
<div>
<details style={{ float: "right" }}>
<summary>Actions</summary>
<button
onClick={async () => {
await chat.leaveChannel(window);
}}
>
Leave channel
</button>
</details>
</div>
<div className="msgs">
{messages.map((message, key) => (
<div key={`${key}-messages`}>
<small>{message.time}</small>{" "}
<small>{message.peerInfo.alias}</small> <p>{message.msg}</p>
<div ref={messagesEndRef} />
</div>
))}
</div>
<div>
<div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") send();
}}
/>
</div>
<div>
<button onClick={send}>Send</button>
</div>
</div>
</div>
</div>
);
};
export default ChatWindowChannel;
Note 1: setMessages(msgs);
do not change the value of messages
immediately, it will be set only on next render so console.log(messages)
just after setting will give you incorrect results.
Note 2: In case the reference of the array msgs
that you are trying to set to a state variable is not changed even if array is modified - re-render will not be executed and useEffects will not be triggered, reference to an array needs to be new.
setMessages([...msgs]);
Same happens also with {objects}
.