React/Jest How to wait until useEffect has run? - javascript

I have the following code:
const doSomething = useCallback(someFunction);
useEffect(() => {
// doSomething takes in the data and invokes the callback at the end as newData
doSomething(data, (newData: string) => {
setData(newData);
});
}, [data, doSomething]);
When I want to test this, it only works when I use setTimeout with 1 or more ms. I suppose this is because the function in useEffect has not run yet and thus has not updated the component, correct me if I'm wrong. How can I work around this without setTimeout? Or do I need a completely different approach? Help is appreciated.

maybe try a wrapper function, for example:
async function handleSomething(){
const res = await doSomthing()
setData(res)
}
then call this function inside useEffect.

Related

I set new state but it remains unchanged in next line function call

I have piece of a code that I don't entirely understand why it acts like that.
const handleLoadMore = () => { setPageNumber(pageNumber+1); fetchData(pageNumber); };
State pageNumber remains same as I didn't change the state. Outside of the handleLoadMore function increments. I kind of understand that it won't increment function body. So, is there anyway to tell fetchData(pageNumber) to wait for state to update.
I tried to find on google how to solve this kind of a problem. I tried setTimeout function but it didn't help.
Do this:
const [pageNumber, setPageNumber] = React.useState(0);
const handleLoadMore = () => {
setPageNumber(number => number + 1);
}
React.useEffect(() => {
fetchData(pageNumber);
},[pageNumber])
You state setter function is happening asynchronously (this is by default with React), meaning it is not executed until the call stack is clear, so after your fetchData call has been made.
Presumably, the handleLoadMore function is event-based, like a button onClick.
So instead, pass the pageNumber + 1 to the fetchData function directly. No need for useEffect.
const handleLoadMore = () => {
setPageNumber(previousNum => previousNum + 1)
fetchData(pageNumber + 1)
}
Important Side-note
When updating state with a reference to itself, it is best practice to use the function argument syntax, to ensure you are not updating based on a stale reference to the state.

Defining MainButton.onClick() for Telegram Webapp correcly

I am trying to define window.Telegram.WebApp.MainButton.onClick() functionality to use the most recent values.
Lets see through code:
// selectedRegions is an array of strings
const [selectedRegions, setSelectedRegions] = React.useState([""]);
React.useEffect(() => {
window.Telegram?.WebApp.MainButton.onClick(() => {
// window.Telegram.WebApp.sendData(selectedRegions);
alert("main button clicked");
});
}, [selectedRegions]);
Now as I update the selectedRegions, this useEffect is called for the number of times the state changes which also updates the MainButton.onClick() functionality. Then at the end, when the MainButton is pressed, in this case, alert() is shown the number of times the state was updated, eg, if the selectedRegions contain 3 values, the alert will be shown 3 times.
What I want is to somehow define this onClick() once or the functionality executed only once, so that I can send the selectedRegions only once with the most recent values back to the bot.
UPDATE 1: After looking into the function onEvent() in telegram-web-app.js file,
function onEvent(eventType, callback) {
if (eventHandlers[eventType] === undefined) {
eventHandlers[eventType] = [];
}
var index = eventHandlers[eventType].indexOf(callback);
if (index === -1) {
eventHandlers[eventType].push(callback);
}
};
It seems to my understanding that if the callback is present in eventHandlers["webView:mainButtonClicked"], then it will not do anything, otherwise just push it into the array.
What I fail to understand is that somehow the callbacks are different, that's why they get appended to the array, justifying the multiple calls to it.
However, I am trying to use MainButton.offClick() to remove the from eventHandlers array, have not succeeded yet. If anyone has done so, it would be highly appreciated.
I have faced exactly same issue. I had some buttons that updates state and on main button click I wanted to send that state, but as you mentioned telegram stores all callbacks in array and when you call sendData function, web app gets closed and only first callback is executed. If you tried to remove function with offClick, I think that didn't worked, because callbacks are compared by reference, but function in component was recreated by react when component is re-rendered, so, that's why offClick failed to remove that function. In this case solution would be like this
const sendDataToTelegram = () => {
window.Telegram.WebApp.sendData(selectedRegions);
}
useEffect(() => {
window.Telegram.WebApp.onEvent('mainButtonClicked', sendDataToTelegram)
return () => {
window.Telegram.WebApp.offEvent('mainButtonClicked', sendDataToTelegram)
}
}, [sendDataToTelegram])
If you are not familiar with this syntax - return in useEffect is callback that is called before new useEffect is called again
Or you can solve this also with useCallback hook to recreate function only when selected region state is changed. Like this:
const sendDataToTelegram = useCallback(() => {
window.Telegram.WebApp.sendData(selectedRegions);
}, [selectedRegions])
useEffect(() => {
window.Telegram.WebApp.onEvent('mainButtonClicked', sendDataToTelegram)
return () => {
window.Telegram.WebApp.offEvent('mainButtonClicked', sendDataToTelegram)
}
}, [sendDataToTelegram])
I use Angular with typescript and I ran into this problem too. But I finally found a solution: for some reason if I pass a callback like this -
"offClick(() => this.myFunc())" the inner "indexOf" of the "offEvent" cannot find this callback and returns "-1".
So I ended up with:
setOnClick() {
let f = () => this.myFunc;
window.Telegram.WebApp.MainButton.onClick(f);
}
setOffClick() {
let f = () => this.myFunc;
window.Telegram.WebApp.MainButton.offClick(f);
}
myFunc() {
console.log('helloworld');
}
Hope this helps.

Using clearInterval() inside Useeffect is not working. The function in setInterval() keeps getting called

Desired Outcome:
I am getting a script's status from an API and then checking what the status is.
If the status = 'XYZ', I need to call the getStatus function again (using setInterval),
else I just need to return/do nothing.
Issue:
The major issue is that clearInterval() is not working and the getStatus() function is getting called after the mentioned interval (10 seconds here) again and again, EVEN THOUGH the status IS NOT EQUAL to 'XYZ'.
Please help out. Thanks in advance.
const [intervalId, setIntervalId] = useState();
useEffect(() => {
getStatus();
}, []);
const getStatus = async () => {
await getScriptStatus()()
.then(response => {
if (response.data[0].status == 'XYZ') {
setIntervalId(
setInterval(() => {
getStatus();
}, 10000)
);
}
else
return () => clearInterval(intervalId);
})
.catch(error => {
errorMessage(error);
});
UPDATE:
Using React.useRef() instead of useState() for intervalId and clearing interval before using setInterval() in the 'if' condition worked or me.
Thank you for the helpful answers.
Use setTimeout instead of using setInterval.
For more details visit : Documentation on setTimeout
intervalId doesn't need to be a "state", rather store is as a ref and not a state.
Notice I changed your function from using .then().catch() to using async/await.
I think that the problem you're facing is caused because React State is asynchronous - the setIntervalId didn't update the intervalId yet, when you're trying to already access intervalId's new value
const intervalId = useRef(null);
const getStatus = async () => {
try {
const response = await getScriptStatus()()
if (response.data[0].status == 'XYZ') {
intervalId.current = setInterval(getStatus, 10000);
}
else return () => clearInterval(intervalId.current);
} catch (error) {
errorMessage(error);
}
}
Another suggestion:
The getStatus function should not act as a useEffect-function, because it's very confusing to see that getStatus returns a function.. and then you need to trail back to "AH it's a useEffect-function.. and the function returned is a cleanup function.. " - not good.. .
so.. My Suggested Options for that:
Either change the getState function to only getStatus.. and then when you call it from inside the useEffect- also handle the case in which you want to return a function (cleanup function)
Less suggested: Have all the content straight inside the useEffect. And since you want to use async/await -> create a self-calling-async-function: useEffect(()=>{ (async () => { await func(); })(); }, []);
Update:
In addition to all that ^
You really don't need the setInterval (like others have suggested). setTimeout will do just fine. But you might still want the clearTimeout (changed from clearInterval) if the functio might be called on different occasions
Main problem here is most probably that you are installing new intervals recursively: function installs interval that calls function that installs interval etc, effectively producing cascade of intervals. These are more or less in-revocable, because their IDs live ephemerally in function scope that installed them and are then thrown away.
You can observe it with slightly modified version of your code in this sandbox.
Change return () => clearInterval(intervalId); to this clearInterval(intervalId);.

Calling a callback function that returns a setTimeOut callback javascript

I tried searching for this, but was unable to find something that matched the case I needed.
I have this function here that can't be modified:
function generate() {
const delay = 7000 + Math.random() * 7000;
const num = Math.random();
return (callback) => {
setTimeout(() => {
callback(num);
}, delay);
};
}
When I try to call the function like generate() I get an error. I've also tried using a promise based approach like:
const result = await generate();
return result;
But when I do that the result that is returned is a promise, which I can't render into JSX (I'm using React).
JSX component code (For debugging purposes currently):
const Test = () => {
return <>{generate()}</>;
};
I would appreciate any suggestions here. Thanks!
This in no way is to solve your problem, but only to understand this logic. Maybe it can help in solving the problem.
generate() in this case returns a function that sets the timeout.
typeof generate() = "function"
// this is then the function we use to run the long running function
const generateFunction = generate();
//we then create the callback handler function
const callback = () => {...}
//when executed it will set the timeout to the random delay and display the result in your component of oyur choice.
generateFunction(callback);
fiddle

return cleanup function from within finally block in react

Within a React function element I have a use Effect hood that has some callback based stuff going on. At the end of the callback chain, I need to return a function to cleanup on dismounting. How is that done.
I have something like this:
ReactFunctionElement=(..)=>{
useEffect(()=>{
asyncCall()
.then(..)
.cath(..)
.finally(
return ()=>{cleanup stuff}
)
},[some filters])
}
But the cleanup stuff never gets run. I don't know how, or if it is even possible, to lift that back out into the useEffect and return.
I think you need something like this:
useEffect(() => {
// Closure variable
const thingToCleanup = null;
asyncCall()
.then(thingToCleanup = thingFromAsync...)
.catch(...);
return () => {
// cleanup stuff using thingToCleanup
});
} ,[some filters]) }
Your useEffect function has to return the clean up function, .finally returns a Promise which won't work but you also don't return that (JavaScript will implicitly return undefined). But you need access to some variables from the setup code in the cleanup so you store those in a closure to use later during clean up.
you can subscribe an api call and in cleanup function you can unsubscribe it. here is an example:
useEffect(() => {
api.subscribe(userId);
return () => api.unsubscribe(userId);
}, [ userId ])

Categories