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

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.

Related

Can anyone give more info about the useEffect hook based on experience

I understand a bit about the useEffect hook but I think there’s still more knowledge to grasp. Some of which are not in the documentation. Please any contribution will help a lot y’all.
Some of my questions are:
Does useEffect get called on initial render even in production just like in development?
If it does, how do we control this the best way?
How can we use a clean up function on this Hook?
How can we make asynchronous calls in useEffect?
My attempts on useEffect usually makes me feel like a bad developer
Please take a look at react docs and react beta docs.
It always runs when your component mounts, after the first render regardless of the environment. In development mode when strict mode is on, it runs twice:
When Strict Mode is on, React will run one extra development-only setup+cleanup cycle before the first real setup. This is a stress-test that ensures that your cleanup logic “mirrors” your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, you need to implement the cleanup function.
I'm not really sure what you mean by controlling it the best way. Your effect or setup code runs whenever the component mounts. Maybe
How to handle the Effect firing twice in development? can help you. You sometimes might want to prevent the effect to be executed when the component mounts, you can skip the effect by using a ref. See this stackoverflow question
The function you return in the useEffect does the clean up for you. See. For instance if you add an event listener inside useEffect, you remove the listener inside the function you return inside of it. See this link
useEffect(() => {
const listener = () => { /* Do something */ };
window.addEventListener("click", listener);
return () => {
window.removeEventListener("click", listener);
};
}, []);
Yes you can. See this stackoverflow question and fetching data in docs
useEffect(() => {
async function asyncFunction() {
/* Do something */
}
asyncFunction();
}, []);
Update:
Take a look at You Might Not Need an Effect
. It explains some situations which you might not need an effect at all.
Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.
Update 2:
You can probably skip this part for now, but it might help you to have a better grasp of useEffect, event handlers and what to expect in the future.
Separating Events from Effects tries to explain the differences between effects and event handlers, why distinguishing between those two is important and using event handlers inside effects.
Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if some value they read, like a prop or a state variable, is different from what it was during the last render. Sometimes, you also want a mix of both behaviors: an Effect that re-runs in response to some values but not others. This page will teach you how to do that.
Sometimes, you might use an event handler which has access to the props or the state inside an effect. But you don't want the useEffect to be triggered every time the values used in the event handler change. The following example is taken form useEffect shouldn’t re-fire when event handlers change
.
function Chat({ selectedRoom }) {
const [muted, setMuted] = useState(false);
const theme = useContext(ThemeContext);
useEffect(() => {
const socket = createSocket('/chat/' + selectedRoom);
socket.on('connected', async () => {
await checkConnection(selectedRoom);
showToast(theme, 'Connected to ' + selectedRoom);
});
socket.on('message', (message) => {
showToast(theme, 'New message: ' + message);
if (!muted) {
playSound();
}
});
socket.connect();
return () => socket.dispose();
}, [selectedRoom, theme, muted]); // 🟡 Re-runs when any of them change
// ...
}
As you see, you do not want to reconnect every time theme or muted variables change. The only time you want the effect(connecting and disconnecting from the server) to run is when the selectedRoom value changes.
So the react team has proposed a RFC: useEvent which provides
A Hook to define an event handler with an always-stable function identity.
useEvent is an experimental and unstable API that has not yet been added to the React(stable versions) ye, so you can’t use it yet.
This might be off-topic but probably helps you to understand React and its lifecycles better: There is this issue useCallback() invalidates too often in practice issue on GitHub . One workaround would be to create a custom hook that returns a function that its identity is stable and won't change on re-renders:
function useEventCallback(fn) {
let ref = useRef();
useLayoutEffect(() => {
ref.current = fn;
});
return useCallback(() => (0, ref.current)(), []);
}
Or you could use the use-event-callback package.
Note that useEventCallback does not mimic useEvent precisely:
A high-fidelty polyfill for useEvent is not possible because there is no lifecycle or Hook in React that we can use to switch .current at the right timing. Although use-event-callback is “close enough” for many cases, it doesn't throw during rendering, and the timing isn’t quite right. We don’t recommend to broadly adopt this pattern until there is a version of React that includes a built-in useEvent implementation.
useEffect is a very powerful hook. Regarding your question:
useEffect(() => (), []) - this version without params will be called once on initial rendering
you can control useEffect with params [] and based on these params you can place some logic inside the callback function.
clean up function used before unmount of your component, it is a good place to remove listeners or close connection to resources like Databases, Camera and etc.
Example of async call
useEffect(() => {
// declare the data fetching function
const fetchData = async () => {
const data = await fetch('https://yourapi.com');
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);
}, [])

React unsubscribe from RxJS subscription in useEffect

A quick question regarding RxJS in useEffect hooks
I've noticed the React community uses this unsubscribe pattern with RxJS:
useEffect(()=>{
const sub = interval(10).subscribe()
return ()=>sub.unsubscribe()
})
I'm curious if this is due to convention/code clarity, or if I'm overlooking something. I would imagine the following would be simpler:
useEffect(()=> interval(10).subscribe().unsubscribe)
However, I could be overlooking something.
Edit: View selected answer. "This" is bound on method call, rather than on subscription instantiation. As a result, unsubscribe fails due to the "this" object not referring to the interval subscription, but rather the useEffect callback environment. Thanks to both contributors. Here is an example of the useEffect hook failing: codesandbox.io/s/morning-bush-7b3m6h?file=/src/App.js
This here:
useEffect(()=>{
const sub = interval(10).subscribe();
return () => sub.unsubscribe();
});
could be re-written as:
useEffect(()=>{
const sub = interval(10).subscribe();
function unsub(){
sub.unsubscribe();
}
return unsub;
});
The key thing to notice is that you're returning a function back to React. unsub isn't called right away, it's called later when the component unmounts.
In fact, you can return arbitrary code to be run later:
useEffect(() => {
/******
* Code that gets called when
* effect is run
******/
return () => { // <- start of cleanup function
/******
* Code that gets called to
* clean up the effect later
******/
} // <- end of cleanup function
});
The problem
I'll rewrite your solution to make talking about the problem clearer. This is semantically equivalent, I've just introduced an intermediate variable.
useEffect(() =>
const sub = interval(10).subscribe();
return sub.unsubscribe;
);
The the question most clearly boils down to: What are the differences between these values? Under which circumstances (if any) will one fail while the other does not.
sub.unsubscribe
() => sub.unsubscribe()
If unsubscribe is a function (isn't bound to an instance of a class/object because it doesn't contain the this keyword), then the two are semantically equivalent.
The issue is that unsubscribe is not actually a function. It's a method on an subscription object. Because of this, the first value above is an unbound method where this is undefined. The moment the method attempts to use its context (this), JavaScript will throw an error.
To make sure that unsubscribe gets called as a method you could do this:
useEffect(() => {
const sub = interval(10).subscribe();
return sub.unsubscribe.bind(sub);
});
You have one less level of indirection this way, though it looks roughly the same.
Furthermore, I would recommend against using bind in most cases. Methods, functions, anonymous lambda functions, and attributes containing any of these three as values all behave differently on various edge cases.
As far as I know, () => a.b() may be needlessly wrapping a function, but will not fail. Plus JIT will optimize this fairly well 99.9% of cases.
Where a.b.bind(a) will fail on a previously bound method, but be optimized 100% of the time. I wouldn't use bind unless it's necessary (and it rarely is)
Update:
Just a quick aside: I use function here to denote a callable block of code which doesn't rely on a context (Doesn't have an object that it references using the this keyword) and a method to denote a callable block of code that DOES rely on some context.
If you prefer other terminology, that's fine. Swap out the words as you read them, I won't take offense, promise :)
Beware of this
The short answer is that the unsubscribe function uses the this keyword and was designed to be called as a property of the subscription object (e.g., as subscription.unsubscribe()) rather than "on its own" (e.g., as just unsubscribe()`).
What is this?
In JavaScript (in addition to the arguments passed to it and the closed-over values in scope) a function may use the this keyword to access a special contextual value. Originally, this was meant to be equal to the "receiver", or the object that the function was being called from. For example, if you set a.f = f and b.f = f with any function f, you can call a.f() and this will be equal to a or call b.f() and this will be equal to b. If you call f() by itself, this will be undefined.
How does that matter?
In this case, interval(10).subscribe() returns a Subscription object with an unsubscribe function that expects this to be that same subscription object. Particularly, this line of the current source code for unsubscribe checks this.closed to avoid re-closing subscriptions that have already been closed.
So, while you're permitted to pull interval(10).subscribe().unsubscribe away and call it without a receiver, you will get an error like "Cannot read properties of undefined (reading 'closed')".
What can I do about it?
Unfortunately, it's not always clear when this is being used in a function that you didn't write. It's best to avoid isolating a function that came from an object (e.g., const f = obj.func;) unless you're sure it's this-free. But if a function is already "on its own", you don't usually have to worry about this.
However, there are the following workarounds:
Wrap it in a new function that always calls it with the expected receiver. This is what your example does: () => sub.unsubscribe().
Create a "bound" copy of the function. Functions are objects too, and available to each function is the bind function that can be used to sort of hard-code the value of this. If you write const unsub = sub.unsubscribe.bind(this);, calling unsub will always call the unsubscribe function with sub as the value of this. This practically very similar to option 1 in that it creates a new function, but this is more streamlined to its purpose.
Use an "arrow" function. This isn't something consumers can do, but the rxjs authors could have written it this way: unsubscribe = () => { ... }. Then you could pull the function out and use it by itself with no errors. This is because "arrow" functions — functions created with the () => ... syntax — have special behavior: they always preserve this from wherever they were created. So when constructing a new Subscription(), it gets a dedicated unsubscribe function whose this value will always equal the constructed subscription object. But since the unsubscribe function is currently declared without the arrow syntax, it uses the typical this-equals-receiver behavior. Furthermore, since it's declared directly in the body of the Subscription class (rather than a value that gets assigned to the property) it's also part of the Subscription prototype and not part of the subscription object itself. Since it's shared across all instances, this can't refer to any specific instance but must look at the receiver.

React won't let me use `useEffect` in a completely reasonable way

I created the following helper functions because functional components in React do not have mount and unmount events. I don't care what people say; useEffect is not a an equivalent. It can be as I demonstrate below:
//eslint-disable-next-line
export const useMount = callback => useEffect(callback, []);
//eslint-disable-next-line
export const useUnmount = callback => useEffect(() => callback, []);
React does not let me do this because I am technically calling useEffect from a non-component function. I'm doing this because when I use useEffect as a mount or unmount event, it pollutes my terminal with meaningless warnings about not including something in the dependency list. I know, I should be doing this...
export default function MusicPlayback(...) {
...
useEffect(() => stopMusic, []);
...
}
But then I get a warning about stopMusic not being included as a dependency. But I don't want it to be a dependency because then useEffect will no longer be an unmount event and stopMusic will be called on every render.
I know that it is eslint that is warning me and I can use //eslint-disable-next-line but that is too ugly to have in every single file that needs an unmount handler.
To my knowledge there is no way to have an unmount handler without using //eslint-disable-next-line absolutely everywhere. Is there some way around this?
Ok, the dependency check is there for a reason, even when you think it shouldn't be there.
useEffect(() => {
stopMusic()
...
}, [stopMusic, ...])
Let's talk about stopMusic, suppose this is a global function from another third party. If the instance never changes, then you should fire it as a dependency, since it won't hurt.
And if the stopMusic instance does change, then you need to ask yourself why you don't want to put it as a dependency, because it might be accidentally calling an old stopMusic.
Now, suppose you are good with all these and still don't want it to be wired with stopMusic, then consider use a ref.
const ref = useRef({ stopMusic })
useEffect(() => ref.current.stopMusic(), [ref])
Either way you get the point, it has to depend on something, maybe your business logic doesn't want to. But technically as long as you need to invoke something which isn't part of the useEffect, it needs to be a dependency. Otherwise from the useEffect perspective, it's an out-of-sync issue. The point of ref (or any object) is to get into this out-of-sync deliberately.
Of course, if you really hate this linter rule, i believe you can disable it.
NOTE
React community is proposing a way in the future to add these dependencies for you behind your back. The rational behind it is that React is designed to be reactive to the data in one-way train.
This is what I ended up having to do to stop the music on unmount.
export default function MusicPlayback(...) {
const [playMusic, stopMusic] = useMagicalSoundHookThingy(myMusic);
const stopMusicRef = useRef(stopMusic);
stopMusicRef.current = stopMusic; // Gotta do this because stopMusic no longer works after render.
...
useEffect(() => {
const stopMusicInHere = stopMusicRef.current; // Doing this to avoid a warning telling me that when called the ref will probably no longer be around.
return stopMusicInHere;
}, [stopMusicRef]);
...
}
Using a ref like this isn't meaningful. It is just a clever hack to fool eslint. We are packaging something that changes on every render into something that doesn't. That's all we are doing.
The problem I am having is that the entity I'm interacting with is static. But the means to communicate with that entity (namely the function stopMusic) is transient. So the brute force means by which useEffect determines dependence isn't nuanced enough to really indicate whether some dependency has actually changed. Just the tiddlywinks that invoke that dependency, the functions and object created by the hooks. Perhaps this is the fault of the hook writer. Maybe the tiddlywinks should maintain the same life cycle as the entity.
I love React very much, but this is an annoyance I've had for a while, and I'm tired of people telling me that I should just include all the dependencies eslint demands as if I don't really understand what dependencies are actually involved. It is probably ideal to never have any side effects at all in a React program, and rely on a data repository pipeline like Redux to provide any context. But this is the real world and there will always be entities with disconnected lifecycles. Music playing in the background is one such entity. Such is life.

Is there any need of async/await in useEffect?

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.

Is it necessary to reject a deferred promise in Angular? [duplicate]

I was writing code that does something that looks like:
function getStuffDone(param) { | function getStuffDone(param) {
var d = Q.defer(); /* or $q.defer */ | return new Promise(function(resolve, reject) {
// or = new $.Deferred() etc. | // using a promise constructor
myPromiseFn(param+1) | myPromiseFn(param+1)
.then(function(val) { /* or .done */ | .then(function(val) {
d.resolve(val); | resolve(val);
}).catch(function(err) { /* .fail */ | }).catch(function(err) {
d.reject(err); | reject(err);
}); | });
return d.promise; /* or promise() */ | });
} | }
Someone told me this is called the "deferred antipattern" or the "Promise constructor antipattern" respectively, what's bad about this code and why is this called an antipattern?
The deferred antipattern (now explicit-construction anti-pattern) coined by Esailija is a common anti-pattern people who are new to promises make, I've made it myself when I first used promises. The problem with the above code is that is fails to utilize the fact that promises chain.
Promises can chain with .then and you can return promises directly. Your code in getStuffDone can be rewritten as:
function getStuffDone(param){
return myPromiseFn(param+1); // much nicer, right?
}
Promises are all about making asynchronous code more readable and behave like synchronous code without hiding that fact. Promises represent an abstraction over a value of one time operation, they abstract the notion of a statement or expression in a programming language.
You should only use deferred objects when you are converting an API to promises and can't do it automatically, or when you're writing aggregation functions that are easier expressed this way.
Quoting Esailija:
This is the most common anti-pattern. It is easy to fall into this when you don't really understand promises and think of them as glorified event emitters or callback utility. Let's recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.
What's wrong with it?
But the pattern works!
Lucky you. Unfortunately, it probably doesn't, as you likely forgot some edge case. In more than half of the occurrences I've seen, the author has forgotten to take care of the error handler:
return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
resolve(result.property.example);
});
})
If the other promise is rejected, this will happen unnoticed instead of being propagated to the new promise (where it would get handled) - and the new promise stays forever pending, which can induce leaks.
The same thing happens in the case that your callback code causes an error - e.g. when result doesn't have a property and an exception is thrown. That would go unhandled and leave the new promise unresolved.
In contrast, using .then() does automatically take care of both these scenarios, and rejects the new promise when an error happens:
return getOtherPromise().then(function(result) {
return result.property.example;
})
The deferred antipattern is not only cumbersome, but also error-prone. Using .then() for chaining is much safer.
But I've handled everything!
Really? Good. However, this will be pretty detailed and copious, especially if you use a promise library that supports other features like cancellation or message passing. Or maybe it will in the future, or you want to swap your library against a better one? You won't want to rewrite your code for that.
The libraries' methods (then) do not only natively support all the features, they also might have certain optimisations in place. Using them will likely make your code faster, or at least allow to be optimised by future revisions of the library.
How do I avoid it?
So whenever you find yourself manually creating a Promise or Deferred and already existing promises are involved, check the library API first. The Deferred antipattern is often applied by people who see promises [only] as an observer pattern - but promises are more than callbacks: they are supposed to be composable. Every decent library has lots of easy-to-use functions for the composition of promises in every thinkable manner, taking care of all the low-level stuff you don't want to deal with.
If you have found a need to compose some promises in a new way that is not supported by an existing helper function, writing your own function with unavoidable Deferreds should be your last option. Consider switching to a more featureful library, and/or file a bug against your current library. Its maintainer should be able to derive the composition from existing functions, implement a new helper function for you and/or help to identify the edge cases that need to be handled.
Now 7 years later there is a simpler answer to this question:
How do I avoid the explicit constructor antipattern?
Use async functions, then await every Promise!
Instead of manually constructing nested Promise chains such as this one:
function promised() {
return new Promise(function(resolve) {
getOtherPromise().then(function(result) {
getAnotherPromise(result).then(function(result2) {
resolve(result2);
});
});
});
}
just turn your function async and use the await keyword to stop execution of the function until the Promise resolves:
async function promised() {
const result = await getOtherPromise();
const result2 = await getAnotherPromise(result);
return result2;
}
This has various benefits:
Calling the async function always returns a Promise, which resolves with the returned value and rejects if an error get's thrown inside the async function
If an awaited Promise rejects, the error get's thrown inside the async function, so you can just try { ... } catch(error) { ... } it like the synchronous errors.
You can await inside loops and if branches, making most of the Promise chain logic trivial
Although async functions behave mostly like chains of Promises, they are way easier to read (and easier to reason about)
How can I await a callback?
If the callback only calls back once, and the API you are calling does not provide a Promise already (most of them do!) this is the only reason to use a Promise constructor:
// Create a wrapper around the "old" function taking a callback, passing the 'resolve' function as callback
const delay = time => new Promise((resolve, reject) =>
setTimeout(resolve, time)
);
await delay(1000);
If await stops execution, does calling an async function return the result directly?
No. If you call an async function, a Promise gets always returned. You can then await that Promise too inside an async function. You cannot wait for the result inside of a synchronous function (you would have to call .then and attach a callback).
Conceptually, synchronous functions always run to completion in one job, while async functions run synchronously till they reach an await, then they continue in another job.

Resources