I am creating a gmail clone with mern and am using socket io to make it realtime aswell as redux for state management
Now when the user creates a mail I listen to it like this
useEffect(() => {
socket &&
socket.on("recieveMail", ({ mail }: any) => {
const localActiveTab = activeTab;
if (mail.mailType === localActiveTab) {
dispatch(addMail(mail));
}
});
}, [socket, dispatch, activeTab]);
addMail
addMail: (state, { payload }) => {
if (state.activeTab === "primary") {
state.primaryMails.splice(0, 0, payload);
} else if (state.activeTab === "promotions") {
state.promotionMails.splice(0, 0, payload);
} else {
state.socialMails.splice(0, 0, payload);
}
},
initialstate
const initialState: initialStateTypes = {
error: "",
primaryMails: [],
promotionMails: [],
socialMails: [],
activeTab: "primary",
isLoading: false,
};
And this works fine, when the user creates a mail it shows said mail only one time but when the user enters capital letters in the subject and the body (I think 2 capitals on body) then it shows that mail 3 times.
Does anyone know why this is happening and how I can fix it?
CodePudding user response:
Quick fix
useEffect(() => {
let isValidScope = true;
socket &&
socket.on("recieveMail", ({ mail }: any) => {
// if message received when component unmounts
// stop setting state in scope previous render cycle
if (!isValidScope) { return; }
const localActiveTab = activeTab;
if (mail.mailType === localActiveTab) {
dispatch(addMail(mail));
}
});
return () => {
isValidScope = false;
// disconnect code
// socket.disconnect()
}
}, [socket, dispatch, activeTab]);
Slighty better maintainable solution
const activeTabRef = useRef(activeTab);
activeTabRef.current = activeTab;
useEffect(() => {
let isValidScope = true;
socket &&
socket.on("recieveMail", ({ mail }: any) => {
// if message received when component unmounts
// stop setting state in scope previous render cycle
if (!isValidScope) { return; }
// the socket connect/reconnect will trigger when
// activeTab state changes, if you dont desire that
// use a useRef to refer to latest value in useEffect event handler
const localActiveTab = activeTabRef.current;
if (mail.mailType === localActiveTab) {
dispatch(addMail(mail));
}
});
return () => {
isValidScope = false;
// disconnect code
// socket.disconnect()
}
}, [socket]);
// dispatch is generally stable reference, can be skipped from depedency array
// if dispatch is not stable, try using a reference with useRef
// remove activeTab, if you dont connect/reconnect on activeTab change
more about useEffect life cycle, to get an idea why
- A new effect is created after every render
- How the cleanup for previous effect occurs before executing of current useEffect
You can read about why isValid is set synchronizing with effects
Why it was running 3 times in dev mode
If you are intererested in taking a deep dive, consider reading a blog post by Dan on useEffect, its old but explanation the details to build a good mental model about useEffects and functional components.
useEvent can solve the problem but it is in RFC
you can check my answer about a implementation to build a custom useEvent till it becomes stable
Hope it helps, cheers