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.
Related
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.
I have a form which, when I submit, should call up 2 functions one by one and then run a condition that is dependent on the mentioned 2 functions.
The handler starts when you press the button, but how do you ensure that they will run sequentially and wait for the result of the previous one?
const handlerNextPage = () => {
controlValidInputs();
handlerSetValues();
inputsData.pages.isValid.page1 && navigate("step2");
};
Thank you
It depends from the nature of your functions and what you are doing inside of them.
If they execute all synchronous tasks, they will be called sequentially and will execute sequentially one after another, that's due to the synchronous single threaded nature of JavaScript engine Event Loop.
BUT
If inside one of those functions you are performing some asynchronous task, like an http fetch or a setTimout, the next function will be executed while the previous one async operations have not been performed yet. To handle those cases you need to use promises.
In your case I don't think you have any asynchronous task in your first function controlValidInputs() and in the second one I assume you are performing some React setState, the React setState is asynchronous but can't be handled with promises, you need to use the useEffect hook to defer operations after a react state update.
if they are synchronous functions so you don't need to worry about they will run one by one but if you are setting state which is asynchronous or calling API in the function which I see that's not the case here. But if you still call an API here in the function you can handle in quite easily by making the function into an async function and putting await but if you setstate and use it value right that is the tricky part because setState need little bit time before setting it so you need to update your logic little bit suppose handlerSetValues(); you setState in this function like this:
const handlerSetValues = () => {
// code here
// code here
// get value of state
setState(value) // this take little bit time so
return value // as well and use it handlerNextPage function
}
const handlerNextPage = () => {
controlValidInputs();
const value = handlerSetValues();
// now use value as state like you use below code I think.
inputsData.pages.isValid.page1 && navigate("step2");
};
you can useeffect
useEffect(()=>{
// inputsData.pages.isValid.page1 && navigate("step2");
},[depency-OnChanging-Which-Call-Code-Inside-Effect])
If a code like this re-render by useEffect's dependency,
// ...
const Test = () => {
// ...
const value1 = "test1"
const func1 = () => {
// do something1
}
useEffect(() => {
const value2 = "test2"
const func2 = () => {
// do something2
}
}, [sth])
return (
// ...
)
}
does value1 & value2 & func1 & func2 reassign to memory?
I'm curious about it, related to optimizing.
Short answer, yes. Every time the function runs the old values will be garbage-collected, new primitive values will be assigned in memory and new references will be created to functions and objects.
But the real question with a "not-that-short" answer is "does it impact performance in a significant way?". And the answer is... depends. In most cases, it will not (see the doc about it). But there are scenarios where you will need to make some changes to have a performance optimization with useCallback and use useMemo.
It's also worth mentioning (as said in Shivam Jha's answer) that a trigger in useEffect no necessarily causes a re-render (DOM paint) because this process happens first on the virtual DOM and will only be persisted in the real DOM when necessary.
I will leave here some other references about this discussion.
Dan Abramov's tweet on memoizing everything (also look at responses)
Kent C. Dodds's article about render performance
Felix Gerschau's article about when render occurs
does value1 & value2 & func1 & func2 reassign to memory?
in short the answer is yes.
it's more clear if you look at it for what it is: Test is a function, so everytime that function is called all the variables inside the function scope (the curly brackets) are re-declared and re-assigned.
Let's dive into the details:
value1 and func1 are in the function's body so they get declared and assigned every time the function is called, they are not related at all, they have just the same name.
value2 and func2 instead are declared inside an useEffect hook with a declared dependency (sth), this means that these 2 variables are redeclared and reassigned only after the first render and after every other render if the sth variable changed its value compared to the previous render.
if you want to optimize value1 so it doesn't change at every render you can use the useMemo hook this way:
const value1 = React.useMemo(() => {
return "test1"; //here you might have a more complicate way to determine value1
}, []); //array of dependencies like for `useEffect`, so `value1` will be recalculated only if any of the values provided in here change. by leaving it empty value1 will always be the **same** variable
you can do similar optimizations with functions too with the useCallback hook
According to docs:
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
Also, it does not re-renders the code but runs the code again when the dependencies passed to it changes
Tip: Optimizing Performance by Skipping Effects describes solving performance problem due tocleaning up or applying the effect after every render.
Also, you can free up allocated memory (if not freed automatically) or run some side effects after running the code (setTimeOut, etc) by using useEffect with cleanup.
Basically do everything you want to run after useEffect inside a return function:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
It seems to me that, in ES6, the following two functions are very nearly identical:
function () {
return this;
}.bind(this);
() => {
return this;
};
The end result seems the same: arrow functions produce a JavaScript function object with their this context bound to the same value as the this where they are created.
Obviously, in the general sense, Function.prototype.bind is more flexible than arrow functions: it can bind to values other than the local this, and it can bind any function's this at any point in time, potentially long after it is initially created. However, I'm not asking how bind itself is different from arrow functions, I'm asking how arrow functions differ from immediately calling bind with this.
Are there any differences between the two constructs in ES6?
There are no (significant) differences.
Well, okay, that's a little premature. There are three tiny differences unique to arrow functions.
Arrow functions cannot be used with new.
This means, of course, that they do not have a prototype property and cannot be used to create an object with the classically-inspired syntax.
new (() => {}) // TypeError: () => {} is not a constructor
This is probably for the best, though—the way new works would not make much sense with bound functions.
Arrow functions do not have access to the special arguments object that ordinary JavaScript functions have access to.
(() => arguments)(1, 2, 3) // ReferenceError: arguments is not defined
This one is probably a little bit more of a gotcha. Presumably this is to remove one of JavaScript's other oddities. The arguments object is its own special beast, and it has strange behavior, so it's not surprising that it was tossed.
Instead, ES6 has splats that can accomplish the same thing without any magic hidden variables:
((...args) => args)(1, 2, 3) // [1, 2, 3]
Arrow functions do not have their own new.target property, they use the new.target of their enclosing function, if it exists.
This is consistent with the other changes to remove "magically" introduced values for arrow functions. This particular change is especially obvious, considering arrow functions can't be used with new anyway, as mentioned above.
Otherwise, arrows are just like bound functions, semantically. It's possible for arrows to be more performant, since they don't have to carry around the extra baggage and since they don't need to be converted from ordinary functions first, but they're behaviorally exactly the same.
There are a few differences:
Arrow functions cannot be constructed. While both arrow functions and bound functions both don't have a .prototype property, the former do throw an exception when called with new while the latter just ignore the bound value and call their target function as a constructor (with the partially applied bound arguments, though) on the new instance.
function F() {}
var f = () => {},
boundF = F.bind({});
console.log(new boundF(), new boundF instanceof F) // {}, true
console.log(new f) // TypeError
Arrow functions do have lexical arguments, new.target and super as well (not only lexical this). A call to an arrow function does not initialise any of those, they are just inherited from the function the arrow function was defined in. In a bound function, they just refer to the respective values of the target function.
Arrow functions don't actually bind a this value. Rather, they don't have one, and when you use this it is looked up like a variable name in the lexical scope. This does allow you to lazily define an arrow function while this is not yet available:
class X extends Object {
constructor() {
var f = () => this, // works
boundF = function(){ return this; }.bind(this);
// ^^^^ ReferenceError
super(); // initialises `this`
console.log(f(), f() == this); // {}, true
}
}
new X;
Arrow functions cannot be generator functions (though they can return generators). You can use .bind() on a generator function, yet there is no way to express this using an arrow function.
Here is one more subtle difference:
Arrow functions can return a value without using the 'return' keyword, by omitting the {} braces following the => immediately.
var f=x=>x; console.log(f(3)); // 3
var g=x=>{x}; console.log(g(3)); // undefined
var h=function(x){x}; console.log(h(3)); // undefined
var i=x=>{a:1}; console.log(i(3)); // undefined
var j=x=>({a:1}); console.log(j(3)); // {a:1}
As I have understood it the following way to call an event handler is
+ compact and simple to read
-, but causes a new myWrapperFunc function to be created on every render
However, creating functions is cheap right? That that minus is insignificant, right?
Am I correct in my understanding that this way to pass an event handler with a parameter will not cause a new handler instance to be created on each render?
handler(event, val) {
...
}
<Component onClick={myWrapperFunc = (e) => handler(e, "myVal")}>
First of all, you are passing the function wrong. It should be:
<Component onClick={(e) => handler(e, "myVal")}>
If we talk about the main question, I'm not an expert but there is still not an agreement on this subject. There is a reality that your callback function is created in every render and this causes a performance loss. But how significant is this loss? This depends on your app probably.
Is it a big app which includes so many components that create callbacks like that. So, you should consider an optimization then. Some people say if you don't need optimization then don't bother with it :) Some says follow the best practices.
You can pass parameters to your functions without using them like that but somehow you should get these parameters in your component. If it is a prop then use it directly instead of passing it like that for example. Then use a separate function and its reference. You don't need to pass e for a callback, it is passed by automatically with callbacks.
handler () => {
use(event);
use(props.val);
use(val_variable_in_component;
...
}
..
<Component onClick={handler}>