React.js and Firebase auth: setTimeout Callback function not executed? - javascript

I have a function handleSubmit that handles registering in Firebase in a react component. Inside, I want to handle errors with my setErrorTimeout function, which has a setTimeout that resets the error automatically after 3 seconds in this case..
The problem is, my Timeout is not executed, e.g the callback function inside the timeout is not being executed after 3 seconds, but everything else is.. why?
const handleSubmit = async e => {
e.preventDefault()
console.log(formDetails)
if (formDetails.password !== formDetails.passwordrepeat) {
setErrorTimeout(setRegisterError, {
message: 'Passwords do not match!',
})
return
}
console.log('Try')
console.log(formDetails.email, formDetails.password)
try {
auth.createUserWithEmailAndPassword(
formDetails.email,
formDetails.password
)
.then(userCredentials => {
if (userCredentials) {
const user = userCredentials.user
let success = user.sendEmailVerification()
console.log('success register:', success)
setRegisterSuccess(
'You registered successfully! please check your email!'
)
setFormDetails({})
}
})
.catch(error => {
console.log('ERROR!')
setErrorTimeout(error)
})
} catch (e) {
setErrorTimeout(e)
}
}
const setErrorTimeout = error => {
console.log('inside timeout!')
setRegisterError(error)
const timer = setTimeout(() => {
console.log('inside cb!')
setRegisterError(null)
}, 3000)
clearTimeout(timer)
console.log('after timeout!')
}

You're clearing the timeout right after you create it here:
const timer = setTimeout(() => {
console.log('inside cb!')
setRegisterError(null)
}, 3000)
clearTimeout(timer)
You probably want that clearTimeout call to be inside the callback, although it's not even strictly needed since the timeout already fired.

Related

Using useEffect with async?

I'm using this code:
useFocusEffect(
useCallback(async () => {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (user.uid) {
const dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
return () => {
off(dbRef);
};
}
}, [])
);
I'm getting this error:
An effect function must not return anything besides a function, which
is used for clean-up. It looks like you wrote 'useFocusEffect(async ()
=> ...)' or returned a Promise. Instead, write the async function inside your effect and call it immediately.
I tried to put everything inside an async function, but then the off() is not being called.
Define the dbRef variable outside the nested async function so your cleanup callback can reference it, and allow for the possibility it may not be set as of when the cleanup occurs.
Also, whenever using an async function in a place that doesn't handle the promise the function returns, ensure you don't allow the function to throw an error (return a rejected promise), since nothing will handle that rejected promise.
Also, since the component could be unmounted during the await, you need to be sure that the async function doesn't continue its logic when we know the cleanup won't happen (because it already happened), so you may want a flag for that (didCleanup in the below).
So something like this:
useFocusEffect(
useCallback(() => {
let dbRef;
let didCleanup = false;
(async() => {
try {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (!didCleanup && user.uid) {
dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
}
} catch (error) {
// ...handle/report the error...
}
})();
return () => {
didCleanup = true;
if (dbRef) {
off(dbRef);
}
};
}, [])
);

Trying to execute an imported Async function but the function is not behaving asynchronously

I am using React to build a website. I have imported an asynchronous function to execute when I press a button. However, the function is not working asynchronously and I really don't understand why.
interact.js:
export const getNFT = async () => {
setTimeout(() => {
console.log('getNFT code execute');
return nft;
}, 2000);
};
const nft = {
tokenURI: 'https://gateway.pinata.cloud/ipfs/QmdxQFWzBJmtSvrJXp75UNUaoVMDH49g43WsL1YEyb',
imageURL: 'https://gateway.pinata.cloud/ipfs/QmeMTHnqdfpUcRVJBRJ4GQ2XHU2ruVrdJqZhLz',
ID: '212'
};
Main.js
import {
getNFT
} from 'interact.js';
// This function is executed when a user clicks on a button
let getAllocatedNFT = async () => {
try {
let response = await getNFT();
console.log('response from server:: '+response);
}catch(e){
console.log(e);
}
};
console:
response from server:: undefined
getNFT code execute // This is executed correctly after 2 seconds
You have to return promise which will resolve your webAPI(setTimeout)
Please use like below:
const getNFT = async () => {
return new Promise(resolve => setTimeout(() => {
console.log("getNFT code execute")
resolve(true)
}, 2000)
);
};

How to get rid of memory leak in react web app

I have this code in my ReactJS web application:
useEffect(() => {
const fetchInfo = async () => {
const res = await fetch(`${api}&page=${page}`);
setLoading(true);
try {
const x = await res.json();
if (page === 1) {
setItems(x);
setAutoplay(true);
} else {
setItems({
hasMore: x.hasMore,
vacancies: [...items.vacancies, ...x.vacancies],
});
}
} catch (err){
console.log(err);
}
setLoading(false);
};
fetchInfo();
}, [page]);
When this component unmounts while running asynchronous function, it throws an error in console.
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.
How can i cancel asynchronous tasks in cleanup.
I'm assuming here that setLoading is the function setting the state after your component has un-mounted, and therefore throwing this warning. If yes, then what you need is a clean-up function.
The function passed to useEffect can return a function, which will be called before the component unmounts (you can think of it as the equivalent of the old componentWillUnmount) - more details here:
https://reactjs.org/docs/hooks-effect.html#example-using-hooks
Now what you probably want is some sort of flag to check whether it is safe for you to call setLoading, i.e, set that flag to be true by default, then set it to false in the return function. Here's a good article that should help:
https://juliangaramendy.dev/use-promise-subscription/
Now I haven't tested this but essentially your code would look something like this:
useEffect(() => {
const fetchInfo = async () => {
let isSubscribed = true;
const res = await fetch(`${api}&page=${page}`);
if (isSubscribed) setLoading(true);
try {
const x = await res.json();
if (page === 1) {
setItems(x);
setAutoplay(true);
} else {
setItems({
hasMore: x.hasMore,
vacancies: [...items.vacancies, ...x.vacancies]
});
}
} catch (err) {
console.log(err);
}
if (isSubscribed) setLoading(false);
return () => (isSubscribed = false);
};
fetchInfo();
}, [page]);

React hooks. Periodic run useEffect

I need periodically fetch data and update it to the screen.
I have this code:
const [temperature, setTemperature] = useState();
useEffect(() => {
fetch("urlToWeatherData")
.then(function(response) {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
return;
response.json().then(function(data) {
console.log(data[0].temperature);
setTemperature(data[0].temperature);
});
})
.catch(function(err) {
console.log("Fetch Error :-S", err);
});
}, [] );
So, is there any neat way to run it every 15 seconds, in example?
Thanks!
Wrap it in an interval, and don't forget to return a teardown function to cancel the interval when the component unmounts:
useEffect(() => {
const id = setInterval(() =>
fetch("urlToWeatherData")
.then(function(response) {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status
);
return;
response.json().then(function(data) {
console.log(data[0].temperature);
setTemperature(data[0].temperature);
});
})
.catch(function(err) {
console.log("Fetch Error :-S", err);
});
), 15000);
return () => clearInterval(id);
}, []);
Just to give a different approach, you can define a custom hook for extracting this functionality into a reusable function:
const useInterval = (callback, interval, immediate) => {
const ref = useRef();
// keep reference to callback without restarting the interval
useEffect(() => {
ref.current = callback;
}, [callback]);
useEffect(() => {
// when this flag is set, closure is stale
let cancelled = false;
// wrap callback to pass isCancelled getter as an argument
const fn = () => {
ref.current(() => cancelled);
};
// set interval and run immediately if requested
const id = setInterval(fn, interval);
if (immediate) fn();
// define cleanup logic that runs
// when component is unmounting
// or when or interval or immediate have changed
return () => {
cancelled = true;
clearInterval(id);
};
}, [interval, immediate]);
};
Then you can use the hook like this:
const [temperature, setTemperature] = useState();
useInterval(async (isCancelled) => {
try {
const response = await fetch('urlToWeatherData');
// check for cancellation after each await
// to prevent further action on a stale closure
if (isCancelled()) return;
if (response.status !== 200) {
// throw here to handle errors in catch block
throw new Error(response.statusText);
}
const [{ temperature }] = await response.json();
if (isCancelled()) return;
console.log(temperature);
setTemperature(temperature);
} catch (err) {
console.log('Fetch Error:', err);
}
}, 15000, true);
We can prevent the callback from calling setTemperature() if the component is unmounted by checking isCancelled(). For more general use-cases of useInterval() when the callback is dependent on stateful variables, you should prefer useReducer() or at least use the functional update form of useState().

How to fix setTimeout behavior over HTTP requests?

On my client app, I'm using Socket IO to check for unread events. I make a request to my backend, which sets a timeout of 5 seconds, then proceeds to check for unread events and sends any back.
// client
socket.on("response", ({ mostRecentMessages }) => {
// do some stuff first
socket.emit("listenForNew", { userId, currentMessagesFromEveryone });
})
// backend
socket.on("listenForNew", ({ userId, currentMessagesFromEveryone }) => {
if (currentMessagesFromEveryone && userId) {
const { MostRecentMessages } = require("./constants/models");
const filteredIds = [];
currentMessagesFromEveryone.forEach(message => {
filteredIds.push(message.conversation._id);
});
console.log("Entered!");
setTimeout(async () => {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", {
mostRecentMessages
});
}, 5000);
}
});
At first, it works fine. It prints Entered! one time for about 4, 5 requests. Then on the 6th, it starts to print Entered! twice.
Why is this happening and what am I doing wrong?
I'm in favor of the below approach:
Wait for X seconds (5 in our use case)
Call an async operation (execution time in unknown)
Wait until async execution complete
Wait for another X seconds before executing the next call
Implementation may be something like that:
const interval = 5000;
function next() {
setTimeout(async () => myAsyncOperation(), interval);
}
function myAsyncOperation() {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", () => {
mostRecentMessages();
next(); // "next" function call should be invoked only after "mostRecentMessages" execution is completed (or a race condition may be applied)
});
}
next();
I haven't compiled this code but I hope that the concept is clear

Categories