Cannot set state in useEffect - reactjs

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

Related

useEffect wont work on conditional change when still in execution of async function

On a page load I have two useEffects. Both are executing at load, where the first one can possibly set a state, that should trigger the second useEffect one further time. But actually it won't.
Actually it should trigger, as it executes in two cases: When i change the order of these useEffects (could be a solution, but why???), or when i comment out the void getOrPostOnWishlist();, thus when removing the async call from the useEffect. But why is that a problem here?
Here some example code snippet with some comments:
...
const setItemIdToBeHandled = (itemId: number | undefined) =>
setState((prevState) => ({...prevState, itemIdToBeHandled: itemId}));
...
// async, called on second useEffect
const getOrPostOnWishlist = async () => {
if (state.itemIdToBeHandled) {
// if there is an item to be handled, retrieve new wishlist with added item
await addItemToNewWishlist(state.itemIdToBeHandled);
} else if (!state.wishlist) {
// if no wishlist is locally present, check if wishlist exists on api
await checkForPresentWishlist();
}
};
// possibly setting state
React.useEffect(() => {
const urlItemId = UrlSearchParamsHelper.wishlistItemId;
if (urlItemId) {
console.log("found item id in url:", urlItemId);
setItemIdToBeHandled(urlItemId);
}
}, []);
// on state change, but also on load
React.useEffect(() => {
console.log("condition:", state.itemIdToBeHandled); // sticks on 'undefined'
void getOrPostOnWishlist(); // when commented out, above console will show 'undefined', and then an itemId (considering the first useEffect sets the state);
}, [state.itemIdToBeHandled]);
This led to the following output:
But when just commenting out the async call in the second useEffect, this led to:
Googled around, and also tried useCallback, but that didn't work. Doesn't seem to be the issue here, since it's somewhat not about the content of the called function, but about the very fact, that the calling useEffect is not even executed.
It feels like even without await inside the useEffect, a useEffect is still blocked, when it has executed an async function.
Or am i missing something? If some more details are needed, let me know

Next JS + Supabase real time subscription subscribes with state "closed"

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,
}

In useEffect, how do I correctly update state first before running subsequent logics?

I ran into an issue where some logic within useEffect runs on mount once before a state update within it is triggered. Example below:
function App() {
const [account, setAccount] = useState("0x123");
useEffect(() => {
async function main() {
let fetchedAccount = await //some fetch logic to get new account
setAccount(fetchedAccount);
}
console.log(account);
let result = await someFunction(account);
}
I realized when running this, my App runs the logic with the predefined state i.e. someFunction("0x123") before running someFunction("updated state from fetchedAccount"). So console.log(account) shows "0x123" once and then once more for the fetchedAccount string.
How can I avoid this behavior and just have the useEffect run someFunction(accounts) after setAccount(fetchedAccount) is done?
Previously when it was a class component, I used this.setState to update the 'account' state and the someFunction(this.state.account) worked correctly. It doesn't run with the predefined state and only runs with the updated value after this.setState.
Thanks in advance!
Try adding another useEffect hook that includes 'account' in the dependency array.
useEffect(() => {
if(account !== "0x123"){
let result = await someFunction(account);
}
}, [account])
This should ensure that someFunction will only run if value of account changes.
edit: hmm, would adding a conditional to check that account is not "0x123" fix your issue?
Also, you could set initial state to undefined.

Graphql subscriptions inside a useEffect hook doesn't access latest state

I'm building a basic Slack clone. So I have a "Room", which has multiple "Channels". A user subscribes to all messages in a Room, but we only add them to the current message list if the new message is part of the user's current Channel
const [currentChannel, setCurrentChannel] = useState(null);
const doSomething = (thing) => {
console.log(thing, currentChannel)
}
useEffect(() => {
// ... Here I have a call which will grab some data and set the currentChannel
Service.joinRoom(roomId).subscribe({
next: (x) => {
doSomething(x)
},
error: (err: any) => { console.log("error: ", err) }
})
}, [])
I'm only showing some of the code here to illustrate my issue. The subscription gets created before currentChannel gets updated, which is fine, because we want to listen to everything, but then conditionally render based on currentChannel.
The issue I'm having, is that even though currentChannel gets set correctly, because it was null when the next: function was defined in the useEffect hook, doSomething will always log that currentChannel is null. I know it's getting set correctly because I'm displaying it on my screen in the render. So why does doSomething get scoped in a way that currentChannel is null? How can I get it to call a new function each time that accesses the freshest state of currentChannel each time the next function is called? I tried it with both useState, as well as storing/retrieving it from redux, nothing is working.
Actually it is related to all async actions involving javascript closures: your subscribe refers to initial doSomething(it's recreated on each render) that refers to initial currentChannel value. Article with good examples for reference: https://dmitripavlutin.com/react-hooks-stale-closures/
What can we do? I see at least 2 moves here: quick-n-dirty and fundamental.
We can utilize that useState returns exact the same(referentially same) setter function each time and it allows us to use functional version:
const doSomething = (thing) => {
setCurrentChannel(currentChannelFromFunctionalSetter => {
console.log(thing, currentChannelFromFunctionalSetter);
return currentChannelFromFunctionalSetter;
}
}
Fundamental approach is to utilize useRef and put most recent doSomething there:
const latestDoSomething = useRef(null);
...
const doSomething = (thing) => { // nothing changed here
console.log(thing, currentChannel)
}
latestDoSomething.current = doSomething; // happens on each render
useEffect(() => {
Service.joinRoom(roomId).subscribe({
next: (x) => {
// we are using latest version with closure on most recent data
latestDoSomething.current(x)
},

Setting state after data load on useEffect

Attempting to update state after data load on useEffect. Able to update a local variable but not state. I am following an example from https://www.robinwieruch.de/react-hooks-fetch-data/ where Robin sets state in a similar way. For some reason, the state variable is never set correctly in my case.
Using AWS Amplify to load graphQL data. Seems to work successfully for local variable but not state variable.
const [serviceTypes, setServiceTypes] = useState([{}]);
let myServiceTypes = [{}]; // try it with a local variable to debug
useEffect(() => {
let unmounted = false;
async function fetchData() {
const result = await API.graphql(
graphqlOperation(queries.listServiceTypes)
);
console.log(result);
console.log('setting service types...');
console.log(result.data.listServiceTypes.items);
setServiceTypes(result.data.listServiceTypes.items);
myServiceTypes = result.data.listServiceTypes.items;
console.log(myServiceTypes); // set correctly
console.log(serviceTypes); // empty
}
if (!unmounted) {
fetchData();
}
return () => {
unmounted = true;
};
}, []);
Expecting serviceTypes to be set to the data loaded. Instead it is empty.
setState does not work synchronously like that. You cannot expect your serviceTypes variable to immediately contain the data right after you set it. It will be updated when the component re-renders. try moving the console.log(serviceTypes); outside of the useEffect.
see https://stackoverflow.com/a/36087156/5273790 for an explanation of setState async.
It's because you aren't thinking about the effect and renders correctly.
Mounting/Render 1
fetchData is called
setServiceTypes and passes the service type to the next render
serviceTypes is empty
Render 2
useEffect is not called
serviceTypes is now what the previous serviceTypes was set to
Try logging out the serviceTypes right before the render/return

Resources