When it comes to events in React, only DOM elements are allowed to have event handlers which make it possible for users to interact with your React app. If you’re familiar with how events work in standard HTML and JavaScript, it should be easy for you to learn how to handle events in React.
Handling events in React is simple but you have to careful about this
context when using class methods as event handlers.
The context this
inside functions can be bound to different objects depending on where the function is being called:
this
is by default bound to the global object (window
in browser).this
will be bound to this object.this
at the call site (call()
, apply()
,bind()
), this
will be bound to that object.this
to the context of the surrounding code where it’s defined.this
will be bound to this object (also call it as constructor function).Classes in JavaScript are just syntactic sugar atop functions that have a prototype property and that implicitly return an object. When new
is called on the function, it calls the function and attaches everything in the function’s prototype to the returned object.
When you define a component using a class, a common pattern is for an event handler to be a method on the class. In JavaScript, class methods are not bound by default. Once a method is passed somewhere separately from the object – this
is lost.
class Foo extends React.Component {
constructor(props) {
super(props)
}
handleClick(event) {
/* "this" will be undefined when event happens */
}
render() {
return <button onClick={this.handleClick}>Click</button>
}
}
If you forget to bind this.handleClick
, the this
value falls back to default binding and is set to undefined
when the event occurs, as class declarations and prototype methods run in strict mode.
(1) When we bind the this
of the event handler to the component instance in the constructor, we can pass it as a callback without worrying about it losing its context.
class Foo extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
/* ... */
}
render() {
return <button onClick={this.handleClick}>Click Me</button>
}
}
(2) You can use arrow functions (this
is bound lexically) with public class fields syntax:
class Foo extends Component {
handleClick = () => {
/* ... */
}
render() {
return <button onClick={this.handleClick}>{'Click me'}</button>
}
}
(3) Or just inline arrow functions (this
is bound lexically):
class Foo extends Component {
handleClick() {
/* ... */
}
render() {
return <button onClick={() => this.handleClick()}>Click Me</button>
}
}
The problem with (3) is that a different function is created each time the component renders might trigger rerendering on lower components if passed as props. It is preferred to go with (1) or (2) approach for better performance.
You can declare a const function either inside the body of the functional component, or outside.
const Foo = () => {
const handleClick = (e) => {
/* ... */
}
return <button onClick={handleClick}>Click Me</button>
}
However, it’s sometimes suggested to avoid declaring functions inside a render function to avoid unnecessary re-renders of pure components. React provides the useCallback
hook, for memoizing callbacks.
const Foo = () => {
const handleClick = useCallback(
(e) => {
/* ... */
},
[
/* deps */
]
)
return <Bar onClick={handleClick}>Click</Bar>
}
React events are named using camelCase, rather than lowercase like in HTML.
<!--HTML-->
<button onclick="activateLasers()"></button>
// React
<button onClick={activateLasers}>
In HTML, you can return false to prevent default behavior. Whereas in React you must call preventDefault() explicitly.
<!--HTML-->
<a href="#" onclick='console.log("The link was clicked."); return false;' />
// React
function handleClick(event) {
event.preventDefault()
/* ... */
}
The event
argument, an instance of SyntheticEvent
, automatically passed into event handler whenever the event is emitted.
React uses SyntheticEvent
as a cross-browser wrapper around the browser’s native event. It has the same interface as the browser’s native event, including stopPropagation()
and preventDefault()
, except the events work identically across all browsers.
If you find that you need the underlying browser event for some reason, simply use the nativeEvent attribute to get it. Every SyntheticEvent
object has the following attributes:
boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
void persist()
DOMEventTarget target
number timeStamp
string type
It achieves high performance by automatically using event delegation. In actuality, React doesn’t attach event handlers to the nodes themselves. Instead, a single event listener is attached to the root of the document. When an event is fired, React maps it to the appropriate component element.
There’s a lot of different kinds of events. React events emulated the W3C suite of events, which includes Keyboard, Mouse, Touch, Clipboard, Form events.
(1) With an arrow function, we have to pass event
argument explicitly:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
(2) But with bind any further arguments are automatically forwarded.
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
(3) You can also use class public fields syntax with curried function.
class Foo extends React.Component {
handleClick = (id) => (e) => {
/* ... */
}
render() {
return <button onClick={this.handleClick(id)} />
}
}
TypeScript is smart enough to infer types related to DOM events and event handlers because declared in its type:
// Declares there is a global variable called 'window'
declare var window: Window & typeof globalThis
// Which is declared as (simplified):
interface Window extends GlobalEventHandlers {
// ...
}
// Which defines a lot of known handler events
interface GlobalEventHandlers {
onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null
// ...
}
If performance is not an issue (and it usually isn’t!), inlining handlers is easiest as you can just use type inference and contextual typing:
const Foo = () => (
<button
onClick={(event) => {
/* event will be correctly typed automatically! */
}}
/>
)
But if you need to define your event handler separately, the @type/react definitions come with a wealth of typing. Type what you are looking for and usually the autocomplete will help you out.
class Foo extends React.Component<Props, State> {
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState({text: e.currentTarget.value})
}
}
Instead of typing the arguments and return values with React.FormEvent<>
and void
, you may alternatively apply types to the event handler itself.
class Foo extends React.Component<Props, State> {
onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
this.setState({text: e.currentTarget.value})
}
}