Is there any need of async/await in useEffect? - reactjs

I was working on a project and was using firestore database from Firebase. I want to fetch data from the database once the page is reloaded. So the below code is working fine even without async await. But I have studied that we need async/await while API/DATA calls, please help me if I am wrong?
useEffect(() => {
db.collection('database_mcq').onSnapshot(snapshot => { setMcq(snapshot.docs.map(doc => doc.data())) })
}, [])

It's not only unnecessary, it's also forbidden - if you try to use an async function, React will throw an error, because the only thing that can be returned from an effect callback should be a function or nothing at all (and not a Promise).
Instead, use .then - if you're using standard Promises - or use whatever other method the API you're using provides for interacting with asynchrony. In your case, onSnapshot is fulfilling that role - there's no need to do anything else special.
Even in standard JavaScript, there's no requirement to ever use async/await, since it's just syntax sugar over .then.

Related

Are async useEffect Callbacks Actually Harmful, or Just a "Smell"?

I can find lots of articles on the web that will tell you to change:
useEffect(async() => {
await doSomethingAsynchronous();
})
into:
useEffect(() => {
const pointlessFunction = async () => {
await doSomethingAsynchronous();
};
pointlessFunction();
})
But what I can't find is any explanation of the actual harm caused by the former.
I understand that React's ESLint rules will complain if you do the former, and I understand that it's complaining because the former might confusingly suggest that useEffect is waiting for the AJAX to complete (when it's not).
In both cases the async function is generating a promise, and in both cases that promise is being ignored (meaning that neither version is actually waiting for doSomethingAsynchronous to complete).
But ... if you understand the above, and can still write working code in spite of it ... is there any actual harm to doing the former?
EDIT:
It seems from the answers so far there is some "harm", but it's only in the form of limiting options. If I go with form #2, I can't:
return a cleanup function (because the async will make the function always return a promise)
wrap the callback with a try/catch
So maybe that's the only harm: form #1 can't error handle or do cleanup. But I'm still curious: aside from limiting my options, if I don't need to cleanup or handle errors, will form #1 actually break anything (ie. cause harm)?
After all, using a one-line arrow function (() => 5) limits your options too ... but we still use them all the time, and just use the more verbose form when we need it. Why not do the same here?
There are a couple of issues.
React expects two possible return values from the callback: nothing (in which case there is no cleanup function to run), or a function (in which case that function is the cleanup function that will be run). If you return something other than those two return values, you're doing something that React doesn't know how to handle - which means that the logic you're implementing may well be buggy - the script-writer is returning a value of an unexpected type, and may be expecting React to do something with it - but it won't.
It's a bit like doing
someArray.forEach((item) => {
// do something
return true;
});
Because forEach ignores the value returned from the callback, any time you see code like this, it's probably a bug (or useless). If some library-writer nowadays decided to implement forEach themselves they might decide to be strict and show a warning when it sees a value returned from the callback.
React is doing something similar. Using an async function (or returning a non-undefined, non-function value) isn't inherently absolutely forbidden and so doesn't produce an error, but it does indicate that there's something wrong, so it produces a warning.
Async functions that reject will result in unhandled rejections, which should absolutely be avoided for the same reason that plain runtime errors should be avoided. If you had
useEffect(async() => {
await doSomethingAsynchronous();
})
and doSomethingAsynchronous rejected, you would end up with an unhandled rejection - because React does not do anything with the returned Promise, and you should implement the error handling yourself.
While the other answers here are great and informative, none directly answered my original question, ie. "Is there any harm to providing useEffect with an async callback?
The answer is ... no, there is no harm.
However, there is an important limitation to that approach, which is that you cannot provide a cleanup function if you use it (because async functions can never return functions, only promises). Also, some devs may be concerned that future versions of React may break when an async callback is given to useEffect.
Both concerns can easily be addressed with a wrapper around useEffect, called something like useAsyncEffect. This allows you to avoid ES Lint issues (and ensure compatibility with future React versions), while still providing a cleanup function.
export const useAsyncEffect = (effect, dependencies, cleanup) => {
// If no dependencies are provided but a cleanup function is, fix args
if (!cleanup && typeof dependencies === 'function') {
cleanup = dependencies;
dependencies = undefined;
}
// Main wrapper code
useEffect(() => {
effect();
return cleanup;
}, dependencies);
};
I think that it is a bad practice to use async functions in use effects because:
The useEffect function expects to receive a function that returns another function to execute once your component is unmounted, rather than a promise.
As far as I know, non-handled async functions may cause some issues with states when your component is re-rendered, but your async function is not completed yet.

InfiniteLoop in useEffect when one of the dependency is a function from useContext

2021 UPDATE
Use a library that makes requests and cache them - react-query, swr, redux-toolkit-query
ORIGINAL QUESTION
I've been struggling with this for quite a long time and didn't find an answer.
I have a component that is the last step of some registration process during which I ask a user to enter its data through several forms. In this component, I send collected data to API. If the request is successful I show ok, if not I show error.
I have useEffect that sends the data. The function that performs this task lives in a context
const { sendDataToServer } = useContext(context)
useEffect(() => {
const sendData = async () => {
setLoading(true)
await sendDataToServer(...data)
setLoading(false)
}
sendData()
}, [sendDataToServer, data])
If I include sendDataToServer in the dependencies list this useEffect would go into an infinite loop, causing endless rerendering. I suppose this is because a reference to the function has a different value on every render.
I can of course redesign the app and do not keep the function in the context, but I do like it and don't consider it a bad practice (correct me if I am wrong)
So what are my options here? How do I keep the flow with the context API, but use useEffect with the correct list of dependencies?
You're right with your guess, that's why we got useCallback for referential equality.
You didn't post the sendDataToServer function, but it should look something like this with useCallback:
const sendDataToServer = useCallback(data => {
// your implementation
}, [your, dependencies])
After that you can safely use it in your useEffect.
I highly recommend Kent C. Dodd's blog posts: When to useMemo and useCallback
Smartassing now: If it's only purpose is sending data to the server (and not changing the app's state), I don't know why it should be part of the context. It could be a custom hook or even a static function.
Btw: There could be another problem: If the data dependency in your useEffect is changed when executing sendDataToServer, you will still have an endless loop (e. g. when you fetch the new data after executing sendDataToServer), but we can't see the rest of the code.

Why does async work in ComponentDidMount lead to visual lag when navigating unless a simple timeOut is awaited?

I have a tabbed react native app and have 2 related questions:
Why does the navigation between screens visually lag when doing async work (such sending a network request) in the target view's componentDidMount method? The official documentation mentions this method as the appropriate place for handling network requests but even when componentDidMount and the functions it calls are declared to be async () => the visual delay when initiating navigation is glaring. This visual delay occurs at the beginning of the navigation, before the target component is pushed or focused and occurs even when the async work does not modify the app state in any way that would affect the rendering of the target component.
Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?
const timeOut = t => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Completed in ${t}`);
}, t);
});
};
componentDidMount = async () => { // async or not, delay occurs
await timeOut(0); //This line prevents navigation lag
NetworkRequest.getData(payload);
}
...
static getData = payload => {
fetch("API_ENDPOINT", {
method: "POST",
body: payload
})
.then(response => response.json())
.then(response => {
//process response
})
}
Whether NetworkRequest.getData is awaited or not, the navigation delay is present without awaiting this artificial mini-promise beforehand. I feel like I'm misunderstanding react native lifecycles or some other key part of the rendering process because I can't imagine why this little await is necessary to 'trick' the method into being visually asynchronous...
EDIT: Now that I know more about Message Queues and Job Queues, it makes sense that a potential source of the visual lag could come from jamming up the Job Queue with a large task. By awaiting a timeOut in an async method, that method effectively becomes the lowest-priority entity in the job queue. It seems weird screen transitions would be non-blocking since the UI is unavailable during this time, but I'm sure the react native developers had a reason... It also seems quite strange that the documentation recommends performing asynchronous work in this method when it will block without this queue relegation voodoo... Is there a better way to specify message/job queue priority on a Javascript Method beyond simple async? Or is awaiting empty timeOuts the only way to indicate lowest-priority processing.
Using expo sdk 34 which uses react-native 0.59
react-navigation 3.11
And React 16.8.3
EDIT: What a coincidence! Experimental support for Suspense for Data Fetching just announced by Dan 5 hours ago :) More here
Seems like your NetworkRequest.getData(payload) function is doing something expensive synchronously. Because JavaScript is single threaded, the next action doesn't happen until this function returns which can cause a lag.
By adding await timeOut(0), you no longer make it synchronous. It's now called in the next event loop which means any synchronous actions before that would finish already.
Why does the navigation between screens visually lag when doing async...
Maybe it can be laggy because you are on dev mode. Please try it on production.
Why does this visual delay disappear entirely when I await a brief timeout before doing the real async work?
I'm not sure what is NetworkRequest.getData doing, I can be something about it, but if it returns a promise, maybe you should try using await on it.
You are also forgetting to add async to your componentDidMount.
async componentDidMount(){
await NetworkRequest.getData(payload);
}

Using the Page Object Model is it better practice to return a promise or to use async/await in the function when the function does not return a value

Hoping to get some feedback on what is the best practice in this situation
(Protractor testing framework using page object model with async/await instead of SELENIUM_PROMISE_MANAGER).
Let say I have a function called setUsername which simply sets the username in a field. I'm wondering is it better practice to use async/await to await the action in the function itself or to return the action. Either way whenever the function is called it will need to be awaited.
option1
this.setUsername = async function (username) {
await usernameInput.sendKeys(username);
}
option2
this.setUsername = function (username) {
return usernameInput.sendKeys(username);
}
Syntax for calling either option
await loginPO.setUsername('admin');
Reasoning: If I go with option1 then I am declaring await twice (in func and when called), which seems unnecessary, but the function behaves more inline with what I expect. If I go with option 2 then await is only used once but it seems wrong to return anything from a function where I only need to set a value and not get anything back.
In my opinion it is better to use option 1, where you will explicit show that your function is async because has some actions that need to be awaited.
So, everyone will understand that for using it function will be needed to resolve a promise.
Also, if your method will have two or more actions that are needed to be awaited, so, you will have to make your function async.

Passing the response of one API call to another API as a parameter in same file in React

I am learing react and I am stuck in my project. I have two APIs. I fetch cookie from one API and I want to use that cookie as a parameter in another API and both API is called in the same file i.e index.js. How to do this?
You are asking about promises. Based on your problem, you already know that API calls are asynchronous. You control async functions by "forcing" them to wait and return their response before proceeding to the next line of code. A promise is an object you use as a wrapper for your API that does this for you. Here's a basic example:
const apiPromise = () => new Promise((resolve, reject)=> {
const apiData = myApiCallForCookies()
resolve(apiData)
})
apiPromise().then(cookies => nextApiCall(cookies))
If you are confused by the syntax with things like .then, MDN docs are your friend.

Resources