Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do event handlers need to be references and not invocations?

In the React tutorial, it says

Doing onClick={alert('click')} would alert immediately instead of when the button is clicked.

class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={() => alert('click')}>
        {this.props.value}
      </button>
    );
  }
}

However, I cannot understand why that would be... can anyone clarify this for me please? Why can't you pass a function invocation as a handler?

like image 266
coffeeak Avatar asked Jun 05 '17 14:06

coffeeak


2 Answers

When you do onClick={alert("click")} that's going to call the alert function and assign the returned value (undefined) to the onClick property. So, what React sees is onClick={undefined} and says: well, that's not a function, why would I add such a handler?

What you want to pass to onClick is a function, not undefined.

Therefore, you have to do: onClick={myFunction} where myFunction can be () => alert("...") as you mentioned, or you can use bind to create a similar function:

onClick={alert.bind(window, "click")}

bind will return a new function which will internally call the alert function with the "click" argument.


A similar case is when you do setTimeout(() => alert("after 1 second"), 1000). setTimeout expects a function. If you do setTimeout(alert("..."), 1000), the alert will be called, indeed, but the setTimeout will receive as first argument undefined (that's what alert returns).

Instead, if you have a function which returns a function, that will work:

// This will be called first and will return a function
const sayHelloTo = name => {
   // This will be the function passed to `setTimeout`
   return () => alert(`Hello ${name}`);
};
setTimeout(sayHelloTo("Alice"), 1000);

You can use it in the same way for the onClick thingy:

onClick={sayHelloTo("Alice")}

This is a very tiny example about what happens in the background (it's just a proof of concept, I'm sure what it actually happens is way better than this):

const elm = {
  onClick: null,
  // The click method can be invoked manually
  // using `elm.click()` or is natively invoked by the browser
  click () {
     if (typeof this.onClick === "function") {
        this.onClick();
     }
  }
};

// If you do:
elm.onClick = alert("click"); // this calls the alert, and returns undefined
elm.onClick === undefined // true

// But when doing:
elm.onClick = () => alert("click");
typeof elm.onClick // "function"

elm.click(); // this will call the alert
like image 59
Ionică Bizău Avatar answered Oct 13 '22 22:10

Ionică Bizău


When you have an event handler it must be a function reference, not a function invocation. This is because internally when the handler is executed, the handler is first evaluated before it can be called. With inline JSX expressions such as when passing an event handler prop, the expression is evaluated when the component is declared, before the handler is called.

Thus, if you pass in an invocation such as alert('click'), it will alert as it is being evaluated. When the event is triggered and the handler is called, it will then attempt to call the return value of alert (which is undefined) -- not good. Instead, you need a function reference, which when evaluated gives you a callable function which is then called as the handler.

Take this pseudocode for an example:

function on(event, handler) {
    //some event listener function that runs the handler when event happens
    //then we call handler when the event happens:
    handler();
}

Imagine if we invoked the function as on('click', alert('test')), then alert('test') would be evaluated and the return value would be passed as the handler, which is undefined. Then you would be trying to call undefined(), not good. Thus, you must pass a function reference, a reference to a callable function.

like image 20
Andrew Li Avatar answered Oct 13 '22 23:10

Andrew Li