I am using React JS (with chakra-ui and @chatscope/chat-ui-kit-react) with Moralis Web3 creating a chat app where I want to:
I am able to get the first step where I call a query when the component is loaded the first time using useEffect
. I store it in a state variable textMsgs
.
The problem is when I subscribe, in the
subscription.on("create", (object) => {
console.log(textMsgs);
});
the console.log result is always empty. Whereas it should show the last array of what I fetch initially.
I then add a
<Button onClick={() => console.log(textMsgs)}>get textMsgs</Button>
to check if the textMsgs can show the array, and it DOES show the array.
I’m really confused why in subscription.on, the textMsgs is empty, but when I click the button, why is it showing the first fetched when component is loaded?
import { Container, Button, Text } from "@chakra-ui/react";
import Moralis from "moralis/dist/moralis.min.js";
import styles from "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
import {
MainContainer,
ChatContainer,
MessageList,
Message,
MessageInput,
} from "@chatscope/chat-ui-kit-react";
import { useState, useEffect } from "react";
export const Messages = ({ roomId, userId }) => {
const [textMessage, setTextMessage] = useState("");
const [textMsgs, setTextMsgs] = useState("");
useEffect(() => {
getMessages();
subscribeMessages();
}, []);
const subscribeMessages = async () => {
const query = new Moralis.Query("Messages");
query.equalTo("roomId", roomId);
const subscription = await query.subscribe();
// on subscription object created
subscription.on("create", (object) => {
console.log(textMsgs);
});
};
// Get textMsgs in this room
const getMessages = async () => {
const Message = Moralis.Object.extend("Messages");
const message = new Moralis.Query(Message);
message.equalTo("roomId", roomId);
const msgResults = await message.find();
var msgs = [];
for (let i = 0; i < msgResults.length; i++) {
var username = await getUsername(msgResults[i].attributes.userId);
var msg = {
msgId: msgResults[i].id,
createdAt: msgResults[i].attributes.createdAt,
userId: msgResults[i].attributes.userId,
textMessage: msgResults[i].attributes.textMessage,
username: username,
};
msgs.push(msg);
}
setTextMsgs(msgs);
};
const getUsername = async (userId) => {
// Query username
const User = Moralis.Object.extend("User");
const user = new Moralis.Query(User);
user.equalTo("objectId", userId);
const userResults = await user.find();
return userResults[0].attributes.username;
};
const sendMessage = (e) => {
var newMsg = {
textMessage: textMessage,
userId: userId,
roomId: roomId,
};
const Message = Moralis.Object.extend("Messages");
const message = new Message();
message.set(newMsg);
message.save().then(
(msg) => {
// Execute any logic that should take place after the object is saved.
//alert("New object created with objectId: " + msg.id);
},
(error) => {
// Execute any logic that should take place if the save fails.
// error is a Moralis.Error with an error code and message.
alert("Failed to create new object, with error code: " + error.message);
}
);
};
return (
<Container>
{/* react chat */}
<div
style={{
height: "100vh",
}}
>
<Button onClick={() => console.log(textMsgs)}>get textMsgs</Button>
<MainContainer style={{ border: "0" }}>
<ChatContainer>
<MessageList>
<MessageList.Content>
{textMsgs &&
textMsgs.map((data, key) => {
return (
<div key={"0." + key}>
<Text
key={"1." + key}
align={data.userId === userId ? "right" : "left"}
fontSize="xs"
>
{data.username}
</Text>
<Message
model={{
message: data.textMessage,
direction:
data.userId === userId ? "outgoing" : "incoming",
sentTime: "just now",
sender: "Joe",
}}
key={"2." + key}
/>
</div>
);
})}
</MessageList.Content>
</MessageList>
<MessageInput
value={textMessage}
placeholder="Type message here"
attachButton={false}
onChange={(text) => {
setTextMessage(text);
}}
onSend={sendMessage}
/>
</ChatContainer>
</MainContainer>
</div>
</Container>
);
};
Here you can see the Messages.js:30 (in subscription.on) and Messages.js:102 (in ) showing different result
I fixed this. So when we want to update an array state variable, instead of trying to copy the variable and use array.push(), we must use the setState function to get the last state and use spread operator to add the new object in the array.
Example This is the state variable, the textMsgs is filled with an array of objects, example [‘apple’, ‘banana’, ‘lemon’]
const [textMsgs, setTextMsgs] = useState([]);
When we want to add another object, instead of doing this first way
var messages = textMsgs;
messages.push('mango');
setTextMsgs(messages);
we should do the second way
var message = 'mango'
setTextMsgs(oldarr => [...oldarr, message])
Because in the first way, somehow when I’m fetching the data from textMsgs it’s empty. I’m not sure why it happened, but if you guys have this issue you want to try the second way.