Immediately invoked arrow function as a react prop - javascript

I have noticed an interesting issue in my React project. I have solved the issue by following the 1st approach but I wish to know the difference between the following callback approaches while passing as a prop:
1. Arrow function (works fine inside render())
changeImage={ () => this.handleImageUploadModal('OPEN') }
2. Function reference (Uncaught RangeError: Maximum call stack size exceeded)
changeImage={ this.handleImageUploadModal('OPEN') }

The first is a function definition, you tell it to "perform this function on change". The important word is 'definition': You dont excecute it, you define it. It doesn't have the 'start' command:
changeImage={ () => {return this.handleImageUploadModal('OPEN')}() }
// If you want it called instantly, you have to start it: --^^
The second one you should read as a parameter. A more obvious example:
showImage={ this.shouldImageBeShown() }
That function will be called instantly to determine wether or not we should show the image, and returns true/false -> showImage={true}.
If you want to enter the functionname without it being triggered, you can remove the () part of the function so that it doesnt get called, only declared:
changeImage={ this.openImageUploadModal }

changeImage={ this.handleImageUploadModal('OPEN') }
This means "call handleImageUploadModal immediately, and pass its return value into the changeImage prop". I'm guessing handleImageUploadModal calls setState, which means the component will rerender and this process repeats.
changeImage={ () => this.handleImageUploadModal('OPEN') }
This means "create a function with the text () => this.handleImageUploadModal('OPEN') and pass it into the changeImage prop". The newly created function is not called (yet), but can be called whenever the component deems it necessary.

Related

React: Callback arrow functions vs direct trigger

I am not exactly sure how to describe it but let's say I have this code <button onClick={() => exampleFunction}>Text</button> and this code <button onClick={exampleFunction}>Text</button>, so what's the difference in both examples working? I noticed that in case of onChange only second example works.
Could anyone clarify it?
In your first example you're not actually invoking exampleFunction. You're providing a function that returns exampleFunction.
This:
() => exampleFunction
is the equivalent of:
function () { // invoked by the underlying component
return exampleFunction; // returned, but never called.
}
To fix it, add the parens:
() => exampleFunction()
In the second form, you're passing exampleFunction directly, which is a function. It gets invoked by the underlying component.

TypeScript / ReactJS / setTimeout: 'this' implicitly has type 'any' because it does not have a type annotation.ts

In my NextJS/React typescript app I'm using a setTimeout.
There is a bug in React apps causing setTimeout's to get called instantly, which I then found a fix in this answer here: ReactJS: setTimeout() not working?
Below is my code, but I'm getting the following typescript error on the this on this.resetNotification
any
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
Board.tsx(158, 7): An outer value of 'this' is shadowed by this container.
#bind
resetNotification(): any {
console.log('resetNotification...');
this.setState({ notificationClass: 'spin-in-notification' });
this.props.setNotification('', false);
}
#bind
private handleNotificationClick() {
this.setState({ notificationClass: 'slide-out-bck-top' });
setTimeout(
function() {
this.resetNotification();
}
.bind(this),
500
);
}
Do it with arrow function on setTimeout for heredate parents props
setTimeout(
() => {
this.resetNotification();
}......
If you still want to use function () {} syntax, you can pass this as the first parameter to the function, along with a type annotation. Like this:
setTimeout(
function(this: Board) {
this.resetNotification();
}
.bind(this),
500
);
I'm assuming since the file is called Board.tsx that your component is <Board>. If not, change the type annotation for this.
Just pass the function as a reference, no need to wrap it in an anonymous function or even bind it, which creates yet another function.
setTimeout(this.resetNotification, 500);
There is no bug in React calling setTimeout instantly, so if you were puzzled by it, consider this.
function doSomething() {/* */}
const a = doSomething() // immediately invokes and assigns a result
const b = doSomething // stores a reference
// call later
const x = a() // error
const y = b() // invokes doSomething and assigns a result
And in your case, the "bug" you mention is basically the same thing.
When you register your setTimeout callback, you mistakenly immediately call it, where instead you should pass a reference to it.
function doSomething() {/* */}
// wrong
setTimeout(doSomething(), 500) // This is basically the same as writing the `a` from above
setTimeout(a, 500) // like this. See the problem? a() cannot be called later.
To fix it, you have three options.
pass a reference
setTimeout(this.resetNotification, 500)
wrap in an anonymous arrow function which is transparent to this,
meaning it captures the outer (parent) this.
note that this wraps your function in another function every time you call this
setTimeout(() => this.resetNotification(), 500)
wrap in a standard anonymous function, but since it comes with it's own this, you must bind it to the this of the parent.
note that this wraps your function in another function AND THEN binds it, which creates a third function every time
setTimeout(function(){this.resetNotification()}.bind(this), 500)
Enter setTimeout
setTimeout(callback, timeout?, param1?, param2?, ...)
It seems people don't realise that setTimeout and setInterval actually accept optional unlimited parameters. The reason is to make calling the callback simpler, so instead of this
setTimeout(
function(){
this.doSomething(true, "string", someVariable)
}.bind(this),
500
)
You can write this
setTimeout(this.doSomething, 500, true, "string", someVariable)
Isn't that beautiful and elegant? 😉
Also you should fix your resetNotification() function's signature to resetNotification(): void not :any as it doesn't return anything.

Callback can not be invoked properly, if the parameter is a bound function

When a function expects a callback as a param, I suppose it make sense to provide a bound function like this
function invokeCb(cb){
cb();
}
function test(x){
console.log(x)
}
const para="xyz";
invokeCb(test.bind(null,para)) //(1)
invokeCb(()=>{test(para)}) //(2)
I can not see anything wrong with (1). However, when it come to real world, I encounter some unexpected behaviour
Here is a example, in redux
store.subscribe(
()=>{(saveState.bind(null,store.getState()))()}
)
can work while
store.subscribe(saveState.bind(null,store.getState()))
can not correctly, i.e. the store.getState() seems never invoked properly
If you need more context. Here:https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
Maybe I missed some subtle differences between the two form, can anyone point it out?
The differences are
the time at which store.getState() is evaluated
the return value (by using braces without return, you suppress it)
let data = "abc";
function invokeCb(cb){
data = "xyz";
console.log(cb());
}
function test(x){
console.log(x);
return x.toUpperCase();
}
invokeCb(test.bind(null, data)); // abc ABC
invokeCb(()=>{ test(data); }); // xyz undefined
After some investigation, I figured out the real cause of the bug!
Here is the code to demonstrate:
function invokeCb(cb){
cb();
}
function test(x){
console.log(x)
}
let counter=0;
function getData(){
return ({counter:counter++})
}
const bindVersion=test.bind(null,getData())
const invokeVersion=()=>test(getData())
//first time
invokeCb(bindVersion) //{counter:0}
invokeCb(invokeVersion) //{counter:1}
//second time
invokeCb(test.bind(null,getData())) //{counter:0}
invokeCb(()=>{test(getData())}) //{counter:2}
//third time
invokeCb(test.bind(null,getData())) //{counter:0}
invokeCb(()=>{test(getData())}) //{counter:3}
Explanation:
When using the bind version, the parameter is fixed. i.e. once
it is bound it can not be changed.
One the other hand, when is function is invoke directly, the
parameter is dynamic ,i.e. it can always get the updated data
Conclusion:
NEVER bind dynamic parameter to a function
In my case, case state is immutable in redux, store.getState() return a new reference every time, using bind, it always get the first reference

Uncaught TypeError: *** is not a function

I have the following JavaScript code automatically generated from a CMS.
...
function setEmail() {
alert("Hello");
}
...
{if(window&&$){$(window).load(function(){var _rsm741573_01 = findControl("CrmTextField_C015");
if(_rsm741573_01)bindEvent('changes',_rsm741573_01,function(s,a){
(function(y,z){
setEmail()(y,z);
})(s,a);
});
})}}
And I always have this error when change happens on the field:
(index):640 Uncaught TypeError: setEmail(...) is not a function
How to declare the function in order to avoid this error?
In your code, you're calling setEmail()(y,z);. That is, you're calling setEmail(), then taking the return value from that function and trying to again call that as a function, passing in y and z. Your setEmail function does not return anything at all, so it's definitely not returning another function which expects two arguments.
From context, it's unclear why you're calling setEmail()(y,z);, so depending on whether that's intentional or not, you either need to remove one set of parens so you're only calling one function, or rewrite setEmail to return another function, depending on what you're actually aiming to accomplish here.

Stop onClick functions executing on mount without using () => function(someVal)

I'm getting really annoyed by this behaviour that occurs onClick:
If I have:
<button onClick={ this.myFunc(myVal) }></button>
This fires as soon as component mounts not when button is clicked, hence I need to do the following in order to fix it:
<button onClick={ () => this.myFunc(myVal) }></button>
But I believe this is not the correct way to achieve this? Also what if I want to actually pass this.myFunc(myVal) down to another component? It would not work.
When you say
<button onClick={ this.myFunc(myVal) }></button>
You are telling React that you want the returned value from executing this.myFunc(myVal) to be assigned to the onClick handler. Instead you, probably want to give it the function with the parameter set as default:
<button onClick={ this.myFunc.bind(this, myVal) }></button>
This binds myVal as the first argument to this.myFunc and ensures that the context is also bound properly as well. Keep in mind that this causes the event parameter to be passed in as the second parameter to the function.
you are calling the function while doing like below, thats why your method gets called immediately after mounting
<button onClick={ this.myFunc(myVal) }></button>
you have to just assign the function like below
<button onClick={ this.myFunc }></button>
if you want to pass variables to that method, add that variable to state and access that from ther.
The onClick attribute expects a function as a parameter.
That is why the second setup IS correct, and 1st one is not (unless myFunc returns a function)..
The following are functions:
(event) => myFunc(myVal, event) // anonymous function, which calls myFunc IF executed
myFunc.bind(this, myVal) // myFunc without parentheses, with parameters - including event - bound
The following is not a function
myFunc(myVal) // == THE RESULT of the function, so == whatever myFunc returns
In an example:
// myFunc is a function that calls another function
// returns length of whatever is passed in
myFunc(val) {
otherFunc()
return (val.length)
}
// here: myFunc is defined, but it has never executed: otherFunc was not called
let myVal = [1,2]
let callback = () => myFunc(myVal)
// here myFunc is still NOT called, neither is otherFunc
// callback is now defined as a (anonymous) function, which, whenever called
// will execute the function myFunc, by passing in the value of myVal
let a = myFunc(myVal)
// here: myFunc WILL be called, so also otherFunc
// at this point a == 2 (the length of the array myVal)
let b = callback()
// here, callback function is executed, and so also myFunc
// at this point b == 2

Categories

Resources