I am working on this helpdesk for a school project using Next JS and Supabase and got stuck on realtime chat between the operator and the client.
I subscribe to the table in useEffect hook and return the unsubscribe function to clean up.
But when i change tickets sometimes the subscription is established but with a state already closed which causes the subscription to stop sending the callbacks.
I think the problem might be with the new subscription being called right after (or maybe even during) the cleanup function which causes even the new one to be closed. But I am not sure how to get around that.
Any ideas?
this is the useEffect used:
useEffect(() => {
getMessages(id)
const MessageSubscription = supabase
.from<definitions['messages']>('messages')
.on('INSERT', (message) => {
getMessages(id)
})
.subscribe()
async function removeMessageSubscription() {
await supabase.removeSubscription(MessageSubscription)
}
return () => {
removeMessageSubscription()
}
}, [])
Probably useEffect fires twice and it may occure some unexpected behaviors for realtime events.
Just disable the strict mode and try again.
// next.config.js
module.exports = {
reactStrictMode: false,
}
Related
I have a page that displays data fetched from a MongoDb through API, in this page, you can modify the data and after that, the page will render again to display the new data. But inspecting the network requests I noticed my react app sends an infinite number of requests, which obviously slows down everything. I read this is caused by this snippet of code:
useEffect(() => {
fetchData();
}, [users]);
I also read I must empty the dependencies array of the useEffect, but If I do so, the page will not re-render if the data changes (for example after inserting a new record in the db).
This is the function I use to get the data from the db:
const [users, setUsers] = useState([]);
async function fetchData() {
const res = await fetch("http://localhost:8000/users/");
if (res.status === 401) {
console.log(res.json);
} else {
setUsers(await res.json());
}
}
How can I fix this? Thanks.
You created an infinite loop:
fetchData calls setUsers, which sets users. The effect reacts to changes to users, and calls fetchData again. ♾️
I don't know your exact use case, but one solution would be to only call fetchData when an actual user interaction has happend in your app that makes you want to fetch new data.
Whenever if there is any asynchronous task performing related to component and that component unmounts then React generally gives this warning -
Can't perform a React state update on an unmounted component This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I found some solutions over internet to use isMount flag (either by using it with useRef or useState) as true and then updating it to false on component unmount. But is that a proper solution as per React site using isMount is a antipattern.
https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
In future version of React, you probably won't need to fix this. As React dev team is going to remove this warning in future release. The main reason being this warning can be false positive sometimes.
As per this commit by Dan Abramov
https://github.com/facebook/react/pull/22114
But what are the solutions to fix this till that version release -
Using isMountState anti-pattern - If someone is checking isMounted in his code to fix this issue, then that person is already too late in performing this check, as this warning indicates the same check is done by React and it failed.
If this issue is because of an asynchronous call. Then one possible solution is to use AbortController API in your code.
AbortController API helps in aborting any ajax call that is already made. Cool stuff. Right?
More details on this can be found here
Abort Controller1
So if it is a fetch API one can use AbortController API like this
useEffect(() => {
const abortController = new AbortController()
// creating an AbortController
fetch(url, {
signal: abortController.signal
})
// passing the signal to the query
.then(data => {
setState(data)
// if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return
// if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort()
// stop the query by aborting on the AbortController on unmount
}
}, [])
If you are using axios, then good news is axios also provides support for AbortController APIs -
const fetchData = async (params) => {
setLoading(true);
try {
const result = await axios.request(params);
// more code here
} catch (curError) {
if (axios.isCancel(curError)) {
return false;
}
// error handling code
}
return null;
};
useEffect(() => {
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
fetchData({
...axiosParams,
cancelToken: source.token
});
return () => {
source.cancel("axios request cancelled");
};
}, []);
I am trying to get the result from the session and set its state in my react component.
But every time I am getting the memory leak problem due to which I am unable to set my session status to true.
My useEffect code looks like this:
useEffect(() => {
let mounted = true;
getSession()
.then(session => {
console.log('session: ', session);
if (mounted) {
setStatus(true);
}
})
.catch(e => {
console.log('inside error or no session found');
console.log(e);
});
return () => {
mounted = false;
};
}, [status, getSession]);
I tried these methods to solve my problem: Boolean Flag to Control the useEffect and AbortController to clean the useEffect, but both of them did not work.
can you please suggest what is going wrong?
The name of your mounted variable suggests you're expecting that effect callback to only fire on first mount, and the cleanup to only fire on dismount, but your dependency array ensures that the cleanup and effect callback happen every time status or getSession changes.
You don't use status in the callback, so you should remove that to avoid triggering the effect when setting it true. If getSession is stable (doesn't change) across the life of the component, you can remove that as well. Then, with an empty dependency array, your callback will only get called on mount, and your cleanup will only get called on dismount.
Just a side note: If you can modify getSession to accept an AbortSignal you can use to tell it to cancel its operation proactively, in general that's preferable. For instance, if you were using fetch (perhaps getSession does under the covers?), you could do that because fetch accepts an AbortSignal. Details: fetch, AbortController, AbortSignal
I have a header (like facebook I suppose) and there is a notifications bell. I want to listen for new notifications on Firbase Firestore.
I've set up a useEffect, but theres an infinite loop for some reason, and I'm not sure why?
Here is the useEffect/useState:
const [subbedToNotifications, setSubbedToNotifications] = useState(false);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
useEffect(() => {
var unsubscribe = () => {
console.log("this function does nothing");
};
console.log("subscribing to notifications");
if (
userData !== null &&
auth.currentUser.uid !== null &&
!subbedToNotifications
) {
setSubbedToNotifications(true);
unsubscribe = db
.collection("users")
.doc(auth.currentUser.uid)
.onSnapshot((snapshot) => {
dispatch({
type: "SET_USERDATA",
userData: snapshot.data(),
});
});
}
// Detach listener
return () => {
console.log("unsubbing from notifications");
unsubscribe();
setSubbedToNotifications(false);
};
}, [subbedToNotifications]);
So, when the component mounts and whenever subbedToNotifications changes, it'll run this use effect. The IF wrapped around the call to Firebase requires userData (where I store things such as username, profile picture, bio etc.), firebase auth to not be empty, as well as subbedToNotifications to be false. When the call to firebase is made, subbedToNotifications is set to true, so Therefore that shouldn't be called again and should only be subscribed once.
Also the return unsubscribes from firebase, however the console logs were repeating:
console.log("subscribing to notifications") and console.log("unsubbing from notifications") constantly, racking up 6k reads to the database.
Can anyone spot my mistake as to why the useEffect is getting into a loop, or perhaps why the Header is constantly mounting and unmounting?
TIA!
Try changing [subbedToNotifications] to []
I'm making a React dashboard that calls an API every minute for updates. Following the many answers in SO, I have this at the moment that sort of works:
const Dashboard = (props) => {
const [stockData, setStockData] = useState([]);
useEffect(() => {
//running the api call on first render/refresh
getAPIData();
//running the api call every one minute
const interval = setInterval(() => {
getAPIData()
}, 60000);
return () => clearInterval(interval);
}, []);
//the api data call
const getAPIData = async () => {
try {
const stdata = await DataService.getStockData();
setStockData(stdata);
}
catch (err) {
console.log(err);
}
};
However I keep getting browser warning
React Hook useEffect has a missing dependency: 'getAPIData'. Either include it or remove the dependency array
Is this a cause for concern (e.g. causing memory leaks)?
Ive tried to fix it:
It doesnt seem possible to not use useEffect (use setInterval directly)
If I remove dependency or put stockData as dependency for useEffect,
I'll see the API call being made every second, so I assume thats not
right.
If I put the api data call block directly in useEffect, the page wont
have the data shown when it loads the first time/or the page refreshed and I have to wait
for a minute.
I found several references on the issue such as
here
and
here
but I couldn't comprehend it given that I've only started using React
a month ago.
Appreciate any help for this!
You can resolve this issue in multiple way:
You can put getApiData in useEffect direct and use it...
You can use useCallBack, useEffect is go to re-render and make mempry leeek issue since every time react render Dashboard its re-create the getAPIData, you can prevent this case by using useCallBack, and you must make sure about dependency, just you need to put what you need...for example: