Use Effect doesn't run on websocket ON - javascript

I am working on a websocket project in react. But when I send a message, the websocket does reload to display new messages. I have to reload my page manually to show the changes.
Here's my use Effect Hook
useEffect(() => {
if (socket.current) {
socket.current.on('msgSent', ({ newMsg }) => {
console.log('MESSAGE SENT', newMsg)
if (newMsg.receiver === openChatId.current) {
setMessages((prev) => [...prev, newMsg])
setChats((prev) => {
const previousChat = prev.find(
(chat) => chat.messagesWith === newMsg.receiver
)
previousChat.lastMessage = newMsg.msg
previousChat.date = newMsg.date
return [...prev]
})
}
})
}
}, [])
When I remove the useEffect dependency (i.e []), It works but it renders the message multiple times on the screen.
Here's the rest of my frontend client code
const openChatId = useRef('')
const auth = useContext(AuthContext)
const queryMessage = new URLSearchParams(search).get('message')
useEffect(() => {
if (!socket.current) {
socket.current = io(process.env.REACT_APP_API)
}
if (socket.current) {
socket.current.emit('join', { userId: auth.user._id })
socket.current.on('connectedUsers', ({ users }) => {
users.length > 0 && setConnectedUsers(users)
})
}
if (chats.length > 0 && !queryMessage) {
history.push(`/messages?message=${chats[0].messagesWith}`, undefined, {
shallow: true,
})
}
return () => {
if (socket.current) {
socket.current.emit('logout')
socket.current.off()
}
}
}, [chats])
useEffect(() => {
const getAllChats = async (token) => {
try {
setLoading(true)
const res = await getChats(token)
if (res) {
setChats(res)
setLoading(false)
}
} catch (err) {
console.log(err)
setLoading(false)
}
}
getAllChats(auth.token)
}, [])
useEffect(() => {
const loadMessages = () => {
socket.current.emit('loadMessages', {
userId: auth.user._id,
messagesWith: queryMessage,
})
socket.current.on('messagesLoaded', async ({ chat }) => {
setMessages(chat.messages)
setBannerData({
firstName: chat.messagesWith.firstName,
lastName: chat.messagesWith.lastName,
profilePicUrl: chat.messagesWith.profilePicUrl,
})
openChatId.current = chat.messagesWith._id
})
socket.current.on('noChatFound', async () => {
const { firstName, lastName, profilePicUrl } = await ChatGetUserInfo(
queryMessage,
auth.token
)
setBannerData({ firstName, lastName, profilePicUrl })
setMessages([])
openChatId.current = queryMessage
})
}
if (socket.current) {
loadMessages()
}
}, [queryMessage])
const sendMsg = (msg) => {
if (socket.current) {
socket.current.emit('sendNewMsg', {
userId: auth.user._id,
msgSendToUserId: openChatId.current,
msg,
})
}
}
The backend works very well. U think my problem is with the useEffect

I fixed it. I was missing the [chats] dependency

Related

Rxdb infinitely pulling in replicateRxCollection

I'm working with rxdb and I have pull and push handlers for the backend I have used supabase
I have setup the code for replication as follows:
replication.ts
import { RxDatabase } from "rxdb";
import { RxReplicationPullStreamItem } from "rxdb/dist/types/types";
import { replicateRxCollection } from "rxdb/plugins/replication";
import { Subject } from "rxjs";
import { supabaseClient, SUPABASE_URL } from "src/config/supabase";
import { DbTables } from "src/constants/db";
import {
blockPullHandler,
blockPushHandler,
} from "./repilicationhandlers/block";
import { CheckpointType, RxBlockDocument, RxBlocksCollections } from "./types";
export async function startReplication(
database: RxDatabase<RxBlocksCollections>
) {
const pullStream$ = new Subject<
RxReplicationPullStreamItem<RxBlockDocument, CheckpointType>
>();
supabaseClient
.from(DbTables.Block)
.on("*", (payload) => {
console.log("Change received!", payload);
const doc = payload.new;
pullStream$.next({
checkpoint: {
id: doc.id,
updated: doc.updated,
},
documents: [doc] as any,
});
})
.subscribe((status: string) => {
console.log("STATUS changed");
console.dir(status);
if (status === "SUBSCRIBED") {
pullStream$.next("RESYNC");
}
});
const replicationState = await replicateRxCollection({
collection: database.blocks,
replicationIdentifier: "supabase-replication-to-" + SUPABASE_URL,
deletedField: "archived",
pull: {
handler: blockPullHandler as any,
stream$: pullStream$.asObservable(),
batchSize: 10,
},
push: {
batchSize: 1,
handler: blockPushHandler as any,
},
});
replicationState.error$.subscribe((err) => {
console.error("## replicationState.error$:");
console.log(err);
});
return replicationState;
}
blockPullHandler:
export const blockPullHandler = async (
lastCheckpoint: any,
batchSize: number
) => {
const minTimestamp = lastCheckpoint ? lastCheckpoint.updated : 0;
console.log("Pulling data", batchSize, lastCheckpoint);
const { data, error } = await supabaseClient
.from(DbTables.Block)
.select()
.gt("updated", minTimestamp)
.order("updated", { ascending: true })
.limit(batchSize);
if (error) {
console.log(error);
throw error;
}
const docs: Array<Block> = data;
return {
documents: docs,
hasMoreDocuments: false,
checkpoint:
docs.length === 0
? lastCheckpoint
: {
id: lastOfArray(docs).id,
updated: lastOfArray(docs).updated,
},
};
};
blockPushHandler:
export const blockPushHandler = async (
rows: RxReplicationWriteToMasterRow<RxBlockDocumentType>[]
) => {
if (rows.length !== 1) {
throw new Error("# pushHandler(): too many push documents");
}
const row = rows[0];
const oldDoc: any = row.assumedMasterState;
const doc: Block = row.newDocumentState;
console.log(row, oldDoc, doc);
// insert
if (!row.assumedMasterState) {
const { error } = await supabaseClient.from(DbTables.Block).insert([doc]);
console.log("Error 1", error);
if (error) {
// we have an insert conflict
const conflictDocRes: any = await supabaseClient
.from(DbTables.Block)
.select()
.eq("id", doc.id)
.limit(1);
return [conflictDocRes.data[0]];
} else {
return [];
}
}
// update
console.log("pushHandler(): is update");
const { data, error } = await supabaseClient
.from(DbTables.Block)
.update(doc)
.match({
id: doc.id,
replicationRevision: oldDoc.replicationRevision,
});
console.log("Error 2", error);
if (error) {
console.log("pushHandler(): error:");
console.log(error);
console.log(data);
throw error;
}
console.log("update response:");
console.log(data);
if (data.length === 0) {
// we have an updated conflict
const conflictDocRes: any = await supabaseClient
.from(DbTables.Block)
.select()
.eq("id", doc.id)
.limit(1);
return [conflictDocRes.data[0]];
}
return [];
};
But the issue is when I start the application and the pull handler is called correctly but it doesn't stop calling the pull handler and it sends continuous request one after another even after it has fetched the documents even when I set hasMoreDocuments to false It keeps sending requests and running the replicator. Is there something wrong with my configuration?
database.ts:
export const createDatabase = async () => {
const database = await createRxDatabase({
name: "sundaedb",
storage: getRxStorageDexie(),
});
await database.addCollections({
blocks: {
schema: blockSchema as any,
conflictHandler: conflictHandler as any,
},
documents: {
schema: documentSchema as any,
conflictHandler: conflictHandler as any,
},
});
database.blocks.preInsert((docData) => {
docData.replicationRevision = createRevision(
database.hashFunction,
docData as any
);
return docData;
}, false);
database.blocks.preRemove((docData) => {
console.log(" PRE REMOVE !!");
console.log(JSON.stringify(docData, null, 4));
const oldRevHeight = parseRevision(docData.replicationRevision).height;
docData.replicationRevision =
oldRevHeight + 1 + "-" + database.hashFunction(JSON.stringify(docData));
console.log(JSON.stringify(docData, null, 4));
return docData;
}, false);
database.blocks.preSave((docData) => {
const oldRevHeight = parseRevision(docData.replicationRevision).height;
docData.replicationRevision =
oldRevHeight + 1 + "-" + database.hashFunction(JSON.stringify(docData));
return docData;
}, false);
return database;
};

React render message to stay at the bottom

Currently working on a 1:1 live chat messenger. when i send a message its shows up at the bottom of chat where i want it to, but upon page refresh it returns to the top of the messenger. How do i set it so newest message is always at the bottom? heres some code i believe the problem is in setActiveChat or or addMessageToConversation.
const sendMessage = (data, body) => {
socket.emit("new-message", {
message: data.message,
recipientId: body.recipientId,
sender: data.sender,
});
};
const postMessage = async (body) => {
try {
const data = await saveMessage(body);
if (!body.conversationId) {
addNewConvo(body.recipientId, data.message);
} else {
addMessageToConversation(data);
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
const addNewConvo = useCallback(
(recipientId, message) => {
setConversations(previousState => previousState.map(convo => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message)
convo.latestMessageText = message.text;
convo.id = message.conversationId;
return convo
}
return convo
}))
},
[setConversations],
);
const addMessageToConversation = useCallback(
(data) => {
// if sender isn't null, that means the message needs to be put in a brand new convo
const { message, sender = null } = data;
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
setConversations((prev) => [newConvo, ...prev]);
}
conversations.forEach((convo) => {
if (convo.id === message.conversationId) {
const convoCopy = { ...convo };
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
return convoCopy;
} else {
return convo;
}
});
setConversations([...conversations]);
},
[setConversations, conversations],
);
const setActiveChat = useCallback((username) => {
setActiveConversation(username);
}, []);

How to called async function in useEffect after submiting in react native?

I would like once the user click on the submit button for the whole "page" to reload and the useEffect function to be called. The current behavior is that when the user clicks the submit button, the useEffect function is not called at all. It is as if it does not reload directly after submission. I don't know if this is due to async and await. I give you the code :
useEffect() :
useEffect(() => {
console.log('test useEffect');
(async () => {
try {
const value = await AsyncStorage.getItem('authToken_Lust');
if(value !== null) {
const decodedValue = jwt_decode(value);
const current_time = Date.now() / 1000;
if(decodedValue.exp < current_time) {
setMessage('Vous n\'êtes plus connecté(e).')
} else {
setMessage('Vous êtes connecté(e)');
}
}
} catch(err) {
console.log(err);
}
})();
}, []);
The code of the function called after the user clicks submit :
const onSubmit = async () => {
setLoading(true)
await fetch('http://192.168.1.36:3000/api/users/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password
})
})
.then((response) => response.json())
.then(async (res) => {
if(res.error) {
setMessage(res.error);
} else {
try {
await AsyncStorage.setItem('authToken_Lust', res.token);
} catch (err) {
console.log(err);
}
}
})
.catch((err) => console.log(err))
.finally(() => setLoading(false));
}
Tanks
Create a state variable to indicate that credentials have been successfully submitted (within this component).
const [submitted, setSubmitted] = useState(1);
Alter this state variable whenever you get a response from your api in onSubmit (to somehow update the component):
try {
await AsyncStorage.setItem('authToken_Lust', res.token);
setSubmitted(submitted+1);
}
Remove the dependency array from your useEffect altogether or change it to respond to submitted value changes;
useEffect(() => {
// ...
}, [submitted]);
you need to make these changes to your codes:
const onSubmit = () => {
setIsSubmitted(true);
setLoading(true);
fetch("http://192.168.1.36:3000/api/users/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
password: password,
}),
})
.then((response) => response.json())
.then(async (res) => {
if (res.error) {
setMessage(res.error);
} else {
try {
await AsyncStorage.setItem("authToken_Lust", res.token);
} catch (err) {
console.log(err);
}
}
})
.catch((err) => console.log(err))
.finally(() => setLoading(false));
};
const asyncEffect = async () => {
try {
const value = await AsyncStorage.getItem("authToken_Lust");
if (value !== null) {
const decodedValue = jwt_decode(value);
const current_time = Date.now() / 1000;
if (decodedValue.exp < current_time) {
setMessage("Vous n'êtes plus connecté(e).");
} else {
setMessage("Vous êtes connecté(e)");
}
}
} catch (err) {
console.log(err);
}
};
useEffect(() => {
if (isSubmitted) {
console.log("test useEffect");
asyncEffect();
}
}, [isSubmitted]);
I changed all my code taking into account your advice that gives :
const [submitted, setSubmitted] = useState(1);
useEffect(() => {
console.log("test useEffect");
asyncEffect();
}, [submitted]);
const asyncEffect = async () => {
try {
const value = await AsyncStorage.getItem("authToken_Lust");
if (value !== null) {
const decodedValue = jwt_decode(value);
const current_time = Date.now() / 1000;
if (decodedValue.exp < current_time) {
setMessage("Vous n'êtes plus connecté(e).");
} else {
setMessage("Vous êtes connecté(e)");
}
}
} catch (err) {
console.log(err);
}
};
const onSubmit = () => {
setSubmitted(submitted+1);
setLoading(true);
fetch("http://192.168.1.36:3000/api/users/login", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
password: password,
}),
})
.then((response) => response.json())
.then(async (res) => {
if (res.error) {
setMessage(res.error);
} else {
try {
await AsyncStorage.setItem("authToken_Lust", res.token);
} catch (err) {
console.log(err);
}
}
})
.catch((err) => console.log(err))
.finally(() => setLoading(false));
};
When I click on the submit button, the console log works fine (I get "test useEffect" on console) but the asyncAffect() function does not seem to be called.
The setMessage in asyncEffect () don't change at all.

proper use of onCompleted for GraphQL mutations

I want to run the query first. The query returns an id which is then required for the mutation. Currently, there's an issue with the order of how both things run from the handleSubmit(). If the mutation is successful, the console should print console.log('Checking');but that does not happen. The only output I get on the console is What's the Idand the value is probably something that was stored in one of my previous attempts. If the id was derived from this particular round of query, I would have seen Workingon the log, but that doesn't happen either.
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery, {
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
onCompleted: () => getFriendId(),
});
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation({
variables: {
input: {
relatedUserId: Number(id),
type: RelationType.Friend,
userId: 5,
},
},
onCompleted: () => addFriend(),
});
const getFriendId = () => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
} else {
if (error) {
setErrorMessage(error.message);
}
}
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
if (addingFriendData) {
console.log('Checking');
console.log(addingFriendData);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
};
const handleSubmit = () => {
loadUsers();
createUserRelationMutation();
};
Before this, I was trying this:
const [id, setId] = useState('');
const [friendEmail, setFriendEmail] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [createUserRelationMutation, { data: addingFriendData, loading: addingFriendLoading, error: addingFriendError }] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
addFriend();
};
const addFriend = () => {
console.log('Whats the Id', Number(id));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(id), type: RelationType.Friend, userId: 7 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{getFriendId();};
However, in this case, the values of the id & other states weren't being updated timely. I was running a graphql query inside getFriendId()that returns an id, followed by a mutation (inside addFriend(), which uses the id, along with an input (email) that the user types in. The problem is that on the first attempt, the mutation works fine and with correct values. However, when I change the email address on the input and run the query/mutation again, the values from my previous attempt are being used.
In the second attempt, the mutation was still using the id that we got in the first attempt.
Edit:
onCompleted: (data) => getFriendId(data),
const getFriendId = (data: any) => {
console.log('Working');
if (data) {
console.log(data);
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
}
Updated Code:
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [loadUsers, { loading, data, error }] = useLazyQuery(LoadUsersQuery);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
},
] = useCreateUserRelationMutation();
const getFriendId = () => {
console.log('Email', friendEmail.toLocaleLowerCase());
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
},
});
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
setId(data.users.nodes[0].id);
addFriend(data.users.nodes[0].id);
}
} else {
console.log('No data');
if (error) {
setErrorMessage(error.message);
}
}
//addFriend();
};
const addFriend = (idd: any) => {
console.log('Whats the Id', Number(idd));
createUserRelationMutation({
variables: {
input: {relatedUserId: Number(idd), type: RelationType.Friend, userId: 9 }
},
});
if (addingFriendData){
console.log('Checking')
console.log(data);
}
if(addingFriendError){
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
const handleSubmit = () =>
{
getFriendId();
};
You don’t need state to store ID, instead pass the Id to addFriend method like show below
const [friendEmail, setFriendEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const _onLoadUserError = React.useCallback((error: ApolloError) => {
setErrorMessage(error.message);
}, []);
const [
createUserRelationMutation,
{
data: addingFriendData,
loading: addingFriendLoading,
error: addingFriendError,
called: isMutationCalled
},
] = useCreateUserRelationMutation();
const addFriend = React.useCallback((idd: Number) => {
console.log('Whats the Id', idd);
createUserRelationMutation({
variables: {
input: { relatedUserId: idd, type: RelationType.Friend, userId: 9 }
}
});
}, [createUserRelationMutation]);
const getFriendId = React.useCallback((data: any) => {
console.log('Email', friendEmail.toLocaleLowerCase());
if (data) {
if (data.users.nodes.length == 0) {
console.log('No user');
setErrorMessage('User Not Found');
} else {
console.log('ID', data.users.nodes[0].id);
addFriend(Number(data.users.nodes[0].id));
}
}
}, [friendEmail, addFriend]);
const [loadUsers] = useLazyQuery(LoadUsersQuery, {
onCompleted: getFriendId,
onError: _onLoadUserError
});
const handleSubmit = React.useCallback(() => {
loadUsers({
variables: {
where: { email: friendEmail.toLocaleLowerCase() },
}
});
}, [loadUsers, friendEmail]);
if (!addingFriendLoading && isMutationCalled) {
if (addingFriendData) {
console.log('Checking')
console.log(data);
}
if (addingFriendError) {
console.log('errorFriend', addingFriendError.message);
setErrorMessage(addingFriendError.message);
}
}
Update
I have updated the above code, please refer to it. I'm assuming useCreateUserRelationMutation does not accept options as argument, if it accepts option then you could use onCompleted and onError just like loadUsers query.

Not triggered catch from action

I have action that always returns Promise.reject:
module.exports = { create: createActionAsync('CREATE_USER', () => {
return Promise.reject({
response: {
type: 'error',
message: 'It will be implemented soon',
},
});
})}
But in component catch block doesn't work:
onAddUser(data) {
const { userActions: { create } = {} } = this.props;
create(data)
.then(() => {})
.catch(err => console.error(err)) // not working

Categories