ApolloClient - LazyQuery is not fired on clean-up effect - reactjs

I have a problem, I try to call a lazyQuery in a clean-up effect, and nothing happens.
When the component is unmounted, the console.log "closing connection..." is correctly printed in the console, but nothing in the Network tab of my browser.
I have checked in Insomnia that my query is correct, tested to fire the query outside of a clean-up effect, it works.
the closeConnection function is not undefined when the return function of the effect is called, I made sure of that too. connectionRef.current is not null nor undefined, because the console log in the if is printed inside the console.
onCompleted & onError are not printed in the console.
My guess is that, since it is the unmounting of the hook, the useLazyQuery is already unmounted when the function is called, leading to this bug where nothing happens...
I cannot find anything on this on stack overflow, and nothing in Apollo client documentation, does anybody have of had the same issue ? Any idea on how I could fix that ?
I need to call this lazyQuery when the component is unmounted, because I have a connection with my server and do not want this connection to be kept alive after the component that calls this hook is unmounted.
const connectionRef = React.useRef(null);
const [closeConnection] = useLazyQuery(closeConnectionQuery);
React.useEffect(() => {
return () => {
if (connectionRef.current !== null) {
console.log("closing connection ...");
const id = connectionRef.current.id;
connectionRef.current = null;
closeConnection({
variables: { id },
onCompleted: () => {
console.log("ON COMPLETED");
},
onError: (error) => {
console.log("ERROR:", error);
},
});
}
};
}, []);
// connectionRef is set somewhere else in the hook, when the connection is made.
I expected the lazyQuery to be called properly.

Related

Cannot set state in useEffect

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

Why is my React UseEffect (that uses Firebase onSnapshot) in an infinite loop, constantly mounting and unmounting?

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 []

Function is executing even after leaving the page React js

When I navigate from home i.e, "/" to "/realtime" useEffect hook start the video from webcam, then I added a function handleVideoPlay for video onPlay event as shown below.
<video
className="video"
ref={videoRef}
autoPlay
muted
onPlay={handleVideoPlay}
/>
For every interval of 100ms, the code inside setInterval( which is inside the handleVideoPlay function) will run, which detects facial emotions using faceapi and draw canvas.
Here is my handleVideoPlay function
const [ isOnPage, setIsOnPage] = useState(true);
const handleVideoPlay = () => {
setInterval(async () => {
if(isOnPage){
canvasRef.current.innerHTML = faceapi.createCanvasFromMedia(videoRef.current);
const displaySize = {
width: videoWidth,
height: videoHeight,
};
faceapi.matchDimensions(canvasRef.current, displaySize);
const detections = await faceapi.detectAllFaces(videoRef.current, new
faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions();
const resizeDetections = faceapi.resizeResults(detections, displaySize);
canvasRef.current.getContext("2d").clearRect(0, 0, videoWidth, videoHeight);
faceapi.draw.drawDetections(canvasRef.current, resizeDetections);
faceapi.draw.drawFaceLandmarks(canvasRef.current, resizeDetections);
faceapi.draw.drawFaceExpressions(canvasRef.current, resizeDetections);
}else{
return;
}
}, 100);
};
The problem is when I go back the handleVideoFunction is still running, so for canvasRef it is getting null value, and it's throwing this error as shown below
Unhandled Rejection (TypeError): Cannot read property 'getContext' of null
I want to stop the setInterval block on leaving the page. I tried by putting a state isOnPage to true and
I set it to false in useEffect cleanup so that if isOnPage is true the code in setInterval runs else it returns. but that doesn't worked. The other code in useEffect cleanup function is running but the state is not changing.
Please help me with this, and I'm sorry if haven't asked the question correctly and I'll give you if you need more information about this to resolve.
Thank you
You need to clear your setInterval from running when the component is unmounted.
You can do this using the useEffect hook:
useEffect(() => {
const interval = setInterval(() => {
console.log('I will log every second until I am cleared');
}, 1000);
return () => clearInterval(interval);
}, []);
Passing an empty array to useEffect ensures the effect will only trigger once when it is mounted.
The return of the effect is called when the component is unmounted.
If you clear the interval here, you will no longer have the interval running once the component is unmounted. Not only that, but you are ensuring that you are not leaking memory (i.e. by indefinitely increasing the number of setIntervals that are running in the background).

How to fix the unmounted component in react hooks

How to fix the error: 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.
here' the code:
useEffect(() => {
let ignore = false;
setTimeout(() => {
const fetchData = async () => {
try {
setLoading(true);
setError({});
const response = await getLatest();
if (!ignore) setData(response['data']);
} catch (err) {
setError(err);
}
setLoading(false);
};
fetchData();
});
return () => {
ignore = true;
};
}, []);
The problem here is when I click the home page while loading the data and click the room page. then the error will appeared. How to fix on it?
You need to clean up your timeout on unmount, otherwise, it tries to execute your hooks to update the internal state of the component after it goes out of scope (I assume functions like setLoading and setError are from a React hook).
To do so, put the output of setTimeout in a variable and then call clearTimeout in the function you return from useEffect. That's the clean up function and it's run when the component gets unmounted. So you need something like this:
React.useEffect(() => {
...
const timeout = setTimeout(...)
return () => {
...
clearTimeout(timeout);
}
}, [])
See the docs for more info: https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect

How to cancel promise in componentWillUnmount() without using this.isMounted?

I have to call API when the user stops typing and it is working perfectly fine. And I have to mount when the enter key is pressed.
I made a Mock Component here which does this.
But, when the component is unmounted it shows the error Cannot call setState on an unmounted component. Previously I handled this error with this.isMounted. Now I was trying to handle it using promise cancelling in componentWillUnmount as mentioned in the React Blog.
this.cancellablePromise = makeCancelable(getSearchResults(word));
this.cancellablePromise.promise
.then(res => {
console.log({ res });
this.setState({ values: res });
})
.catch(err => console.log("error", err));
console.log("in data ", this.cancellablePromise);
}
The cancellablePromise gets assigned after the promise got resolved. So there is a null object in componentWillUnMount for cancellablePromise instance.
Looks like you just need to do the following thing:
myAwesomeMethod = () => {
this.cancellablePromise = makeCancelable(getSearchResults(word));
this.cancellablePromise
.then(...)
.catch(...)
}
componentWillUnmount() {
this.cancellablePromise && this.cancellablePromise.cancel();
}
this.cancellablePromise will be undefined only in the case when you unmount your component before the call of the method with promise call.
The issue is that your handleChange method calls debouncedGetSearchResult, but you are initializing this.cancellablePromise in getSearchResult. This leaves a 500ms window for the unmount to happen before you have initialized this.cancellablePromise.
You need to rework this so that the debouncing is part of the cancelable promise rather than prior to it.

Resources