how to clear setTimeOut when component unmounts - javascript

i try to get Items data. if request response less than 1 i have to request again. so i write a recursive function with setTimeout. but when i try to change my route function keeps working. window.clearTimeout() or global.clearTimeOut() not worked here when component unmounts. Do i miss something?
useEffect(() => {
getItems(params);
return () => {
window.clearTimeout();
global.clearTimeout();
}
}, []);
const getItems = async(params) => {
try {
const { data = []} = await axios.get('/some-endpoint',params);
dispatch({ type: ITEMS_START });
if (data.length === 0) {
setTimeout(() => {
getItems(params);
}, 5000);
} else {
dispatch({ type: ITEMS_SUCCESS, payload: { data } });
}
} catch (error) {
dispatch({ type: ITEMS_ERROR, payload: error });
}
}

Use a ref to store the timeout ID and then clear that timeout.
const timeoutRef = React.useRef();
useEffect(() => {
getItems(params);
return () => {
window.clearTimeout(timeoutRef.current);
}
}, []);
const getItems = async(params) => {
try {
const { data = []} = await axios.get('/some-endpoint',params);
dispatch({ type: ITEMS_START });
if (data.length === 0) {
timeoutRef.current = setTimeout(() => {
getItems(params);
}, 5000);
} else {
dispatch({ type: ITEMS_SUCCESS, payload: { data } });
}
} catch (error) {
dispatch({ type: ITEMS_ERROR, payload: error });
}
}

Create a reference you can set your timeout too that the unmount can call back to.
let timeout = null;
useEffect(() => {
getItems();
return () => {
if(timeout)
clearTimeOut(timeout)
}
})
const getItems = () => {
timeout = setTimeOut(() => work, 5000);
}
This is the general idea.

Each SetTimeout ( and setInterval ) returns a number which can be used to clear it. ie, var x = setTimeout(() => console.log('timeout'),1000); clearTimeout(x); will do nothing.

Related

Cleanup setTimeout inside useffect async function

I'm developing an autosave component in React.
My logic is whenever the "bestOf" changes, I check with an async fetch function if the data is equal to the data that is stored in my database.
If it is equal, I set my state to saved
If it is not equal, I set my state to unsaved and then I want to wait 5 seconds before calling my handleSave function
I am now trying to cleanup this timeout in order to not call this function multiple times if multiple modifications to the bestOf occurs but the useEffect cleanup function doesn't work in my case.
Here is my try with clearTimeout
useEffect(() => {
let timeout;
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
.then((res) => res.json())
.then((data) => {
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave("saved");
} else {
setSave("unsaved");
timeout = setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
clearTimeout(timeout);
};
}, [bestOf]);
And here is another try with AbortController
useEffect(() => {
const controller = new AbortController();
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`, {
signal: controller.signal,
})
.then((res) => res.json())
.then((data) => {
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave("saved");
} else {
setSave("unsaved");
setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
controller.abort()
};
}, [bestOf]);
Both solutions doesn't work and the timeout function is executed multiple time for each modification. Any suggestions ?
After a lot of tries i finally found that I need to add a flag to ignore the stuff after fetch in the timeout for if bestOf changes.
Otherwise because it is async, the timeout would be set after I have cleared it.
useEffect(() => {
let shouldIgnore = false;
let timeout;
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
.then((res) => res.json())
.then((data) => {
if (shouldIgnore) {
return;
}
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave('saved');
} else {
setSave('unsaved');
timeout = setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
shouldIgnore = true;
clearTimeout(timeout);
};
}, [bestOf]);

how to await the code in the setTimeout function till I get the response from an another async function

I am making a splash screen in react native and my code in the setTimeout function runs before I get the response from the getUser function.
Also when I logged userrrrrrrr I get a object with unknown value like this - {"_U": 0, "_V": 0, "_W": null, "_X": null} userrrrrrrr
What I want is that the code in the setTimeout function to run only when I get proper response from the getUser Function.
//
const getUser = async () => {
try {
const jsonValue = await AsyncStorage.getItem('currentUser');
if (jsonValue) {
console.log(jsonValue, 'jjjjjj');
return jsonValue;
} else {
console.log('else');
return false;
}
} catch (e) {
console.log('catch');
return false;
}
};
useEffect(() => {
setTimeout(() => {
if (getUser()) {
console.log(getUser(), 'userrrrrrrr');
navigation.replace('Drawer');
} else {
console.log(getUser(), 'userrrrrrrr111111');
navigation.replace('Login');
}
}, 5000);
}, []);
//
useEffect(async () => {
const user = await getUser()
setTimeout(() => {
if (user) {
console.log(getUser(), 'userrrrrrrr');
navigation.replace('Drawer');
} else {
console.log(getUser(), 'userrrrrrrr111111');
navigation.replace('Login');
}
}, 5000);
}, []);
const getUser = async () => {
try {
const jsonValue = await AsyncStorage.getItem('currentUser');
if (jsonValue) {
console.log(jsonValue, 'jjjjjj');
return jsonValue;
} else {
console.log('else');
return false;
}
} catch (e) {
console.log('catch');
return false;
}
};
useEffect(aync () => {
setTimeout(() => {
const result = await getUser()
if (result) {
navigation.replace('Drawer');
} else {
navigation.replace('Login');
}
}, 5000);
}, []);
One way would be to save the getUser response in a state variable and let useEffect run when that state changes. In your setTimeout, check if your user state holds a valid value (non-null) before executing your logic.
const [user, setUser] = useState(null) // Default value of null
const getUser = async () => {
...
// When you get a successful response, update the state
setUser(response)
...
}
useEffect(() => {
getUser()
}, []) // No dependencies so will run only when the component mounts
useEffect(() => {
setTimeout(() => {
if (user != null) { // Check user validity here!
console.log({ user });
navigation.replace('Drawer');
} else {
console.log({ user }); // Should be null
navigation.replace('Login');
}
}, 5000);
}, [user]) // Runs whenever local user state updates
You may not need setTimeout at all with the above approach, you can just navigate when you have user data in the state -
const [loading, setLoading] = useState(true)
const getUser = async () => {
setLoading(true)
...
// When you get a successful response, update the state
setUser(response)
setLoading(false)
...
}
useEffect(() => {
if (!loading) {
if (user != null) { // Check user validity here!
console.log({ user });
navigation.replace('Drawer');
} else {
console.log({ user }); // Should be null
navigation.replace('Login');
}
}
}, [user])

I get Error: Can't perform a React state update on an unmounted component even though i created cleanup

An error keeps bothering me on my app says
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 do declare a useEffect in my Context so that I can have a realtime data storing and getting for my app.
Here is my code in Context;
const FetchProvider = ({ children }) => {
const [userList, setUserList] = useState([]);
const [teamList, setTeamList] = useState([]);
const authContext = useContext(AuthContext);
const authAxios = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
useEffect(() => {
let isCancelled = false;
if (
authContext.isAuthenticated &&
authContext.authState.userInfo !== null
) {
const getUsers = async () => {
try {
const users = await authAxios.get('/admin/get-all-users');
if (!isCancelled) {
setUserList(users.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
const getTeams = async () => {
try {
const teams = await authAxios.get('/get-all-teams');
if (!isCancelled) {
setTeamList(teams.data);
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
getTeams();
}
return () => {
isCancelled = true;
};
}, [authAxios, authContext]);
return (
<Provider
value={{
authAxios,
userList,
setUserList,
teamList,
setTeamList,
}}
>
{children}
</Provider>
);
};
And I get this error in my Login.jsx and in my even though I don't declare useEffect in submitting and declaring it in .
Here is my code;
const submitCredentials = async (credentials, resetForm) => {
try {
setLoginLoading(true);
const { data } = await publicFetch.post('signin', credentials);
authContext.setAuthState(data);
setSignupSuccess(data.message);
setSignupError('');
setOpen(true);
setTimeout(() => {
setLoginLoading(false);
setredirectOnlogin(true);
resetForm(true);
}, 400);
} catch (error) {
setLoginLoading(false);
const { data } = error.response;
setSignupError(data.message);
setSignupSuccess('');
setOpen(true);
}
return setLoginLoading(false);
};
And I have tried many ways the internet has offered to fix this up but unfortunately it does not fix my problem.
I do have useEffect in my UsersTable.jsx and TeamTables.jsx.
Here is my code in UsersTable.jsx;
useEffect(() => {
let isCancelled = false;
const getUsers = async () => {
try {
const users = await fetchContext.authAxios.get('/admin/get-all-users');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setUserList(users.data);
}
}
return () => {
isCancelled = true;
};
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getUsers();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
Here is my useEffect code in my TeamTable.jsx;
useEffect(() => {
let isCancelled = false;
const getTeams = async () => {
try {
const teams = await fetchContext.authAxios.get('get-all-teams');
setIsLoaded(true);
if (isLoaded === true) {
if (!isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!isCancelled) {
console.log(error);
}
}
};
getTeams();
return () => {
isCancelled = true;
};
}, [fetchContext, isLoaded]);
The isLoaded is used as an AJAX
Well, you can use the React recommended way to fix this issue. All you need to do is wrap your api call within makeCancellable method and cancel them when your component is unmounting.
Ref: https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
To do that create
const makeCancelable = (promise) => {
let isCancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => isCancelled ? reject({isCanceled: true}) : resolve(val),
error => isCancelled ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
isCancelled = true;
},
};
};
create a variable for the request outside your useEffect
let fetchTeamsRequest = null;
and updated your useEffect function like below.
useEffect(() => {
const getTeams = async () => {
if (fetchTeamsRequest) {
try {
await fetchTeamsRequest.promise;
return;
} catch (error) {
return;
}
}
fetchTeamsRequest = makeCancellable(fetchContext.authAxios.get('get-all-teams'));
try {
const teams = await fetchTeamsRequest.promise;
fetchTeamsRequest = null;
setIsLoaded(true);
if (isLoaded === true) {
if (!fetchTeamsRequest.isCancelled) {
fetchContext.setTeamList(teams.data);
}
}
} catch (error) {
if (!fetchTeamsRequest.isCancelled) {
fetchTeamsRequest = null;
console.log(error);
}
}
};
getTeams();
return () => {
if (fetchTeamsRequest) {
fetchTeamsRequest.cancel();
}
};
}, [fetchContext, isLoaded]);

cancel multiple promises inside a promise on unmount?

hi i want to cancel promise on unmount since i received warning,
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 the componentWillUnmount method.
My code:
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
(val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
(error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
useEffect(() => {
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split('toolbox-talks/');
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log('resolve 1');
setFileUri(filesystemUri);
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log('resolve 2');
setFileUri(imageObject.uri);
}
// otherwise download to cache
} catch (err) {
console.log('error 3');
setFileUri(uri);
}
};
const cancelable = makeCancelable(initialize());
cancelable.promise
.then(() => {
console.log('reslved');
})
.catch((e) => {
console.log('e ', e);
});
return () => {
cancelable.cancel();
};
}, []);
but i still get warning on fast press, help me please?
You're cancelling the promise, but you are not cancelling the axios call or any of the logic that happens after it inside initialize(). So while it is true that the console won't print resolved, setFileUri will be called regardless, which causes your problem.
A solution could look like this (untested):
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
}
};
};
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split("toolbox-talks/");
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log("resolve 1");
return filesystemUri;
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log("resolve 2");
return imageObject.uri;
}
// otherwise download to cache
} catch (err) {
console.error("error 3", err);
return uri;
}
};
useEffect(() => {
const cancelable = makeCancelable(initialize());
cancelable.promise.then(
fileURI => {
console.log("resolved");
setFileUri(fileURI);
},
() => {
// Your logic is such that it's only possible to get here if the promise is cancelled
console.log("cancelled");
}
);
return () => {
cancelable.cancel();
};
}, []);
This ensures that you will only call setFileUri if the promise is not cancelled (I did not check the logic of makeCancelable).

Nodejs - Retry same function on error callback in production

I have a javascript function with setTimeOut and I am retrying to call same function, if any error from API call.I am calling same function in catch block.Is my node server is going crash and resources will be blocked or it will keep calling getData() function
let retry = ()=> {
setTimeout(() => {
getData()
retry()
}, 3000);
}
let getData = () =>{
Someapi.getData().then((token) => {
console.log(`Data after 3 seconds->${token}`)
}).catch((err) => {
getData()
})
}
I do not know if this work.
let retry = () => {
setTimeout(() => {
getData();
retry();
}, 3000);
};
while (true) {
let getData = () => {
Someapi.getData()
.then(token => {
console.log(`Data after 3 seconds->${token}`);
return false;
})
.catch(err => {
return true;
});
};
}
I use this retry code in my project, it works well in production:
const pause = (duration) => {
return new Promise(resolve => setTimeout(resolve, duration));
};
const retry = (retryTimes, func, delay) => {
return func().catch(
(err) => {
if(retryTimes > 0) {
return pause(delay).then(
() => retry(retryTimes - 1, func, delay * 2)
);
} else {
return Promise.reject(err);
}
}
);
};

Categories

Resources