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;
CodePudding user response:
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}
.
CodePudding user response:
Can you try something like this:
setMessages([...msgs]);
instead of setMessages(msgs);
CodePudding user response:
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass. In that case you can't view (console.log) immediately after using state setter function.