Skip to content

Latest commit

 

History

History
473 lines (384 loc) · 10.9 KB

events.md

File metadata and controls

473 lines (384 loc) · 10.9 KB

React Events

⚠️ When I started learning React, it was all class components. As a result, most of the examples here use class components. I have left them in tact in case I ever have to deal with legacy code. That being said, the concepts are basically the same and when necessary, I've added function component examples as well and/or links to other notes.

Table of Contents

Introduction

In React, every JSX element has built-in attributes representing every kind of browser event. They are camel-case and take callback functions as event listeners.

<button onClick={this.handleClick}>Click me</button>

Event Types

Any event that can be used in JavaScript exists in React. See here for the complete list of supported events and related properties.

Clipboard Events
onCopy
onCut
onPaste

Composition Events
onCompositionEnd
onCompositionStart
onCompositionUpdate

Keyboard Events
onKeyDown
onKeyPress
onKeyUp

Focus Events
onFocus
onBlur

Form Events
onChange
onInput
onInvalid
onSubmit

Mouse Events
onClick
onContextMenu
onDoubleClick
onDrag
onDragEnd
onDragEnter
onDragExit
onDragLeave
onDragOver
onDragStart
onDrop
onMouseDown
onMouseEnter
onMouseLeave
onMouseMove
onMouseOut
onMouseOver
onMouseUp

Pointer Events
onPointerDown
onPointerMove
onPointerUp
onPointerCancel
onGotPointerCapture
onLostPointerCapture
onPointerEnter
onPointerLeave
onPointerOver
onPointerOut

Selection Events
onSelect

Touch Events
onTouchCancel
onTouchEnd
onTouchMove
onTouchStart

UI Events
onScroll

Wheel Events
onWheel

Media Events
onAbort
onCanPlay
onCanPlayThrough
onDurationChange
onEmptied
onEncrypted
onEnded
onError
onLoadedData
onLoadedMetadata
onLoadStart
onPause
onPlay
onPlaying
onProgress
onRateChanged
onSeeking
onStalled
onSuspend
onTimeUpdate
onVolumeChange
onWaiting

Image Events
onLoad
onError

Animation Events
onAnimationStart
onAnimationEnd
onAnimationIteration

Transition Events
onTransitionEnd

Other Events
onToggle

Event Data

Each category/set of events has one or more properties that can be accessed through the event object. For example:

class TestComponent extends Component {
  constructor(props) {
    super(props);
  }
  handleKeyUp(e) {
    console.log('Key pressed: ', e.key);
    console.log('Key code: ', e.keyCode);
    console.log('alt key: ', e.altKey);
    console.log('ctrl key: ', e.ctrlKey);
    console.log('shift key: ', e.shiftKey);
    // Key pressed:  D
    // Key code:  68
    // alt key:  false
    // ctrl key:  false
    // shift key:  true
  }
  render() {
    const styles = {};
    return (
      <div className="TestComponent">
        <input onKeyUp={this.handleKeyUp} placeholder="type something" />
      </div>
    );
  }
}

You can also access the JavaScript standard e.target, e.target.value, etc.

this binding

Because of the way event callbacks are handled by React, in order to use the this keyword within a callback function (for example to change state with this.setState()), we need to either bind this in the constructor or use arrow syntax and babel as discussed in state.md.

class Button extends Component {
  constructor(props) {
    super(props);
    // we need to bind `this`
    this.handleClick = this.handleClick.bind(this);
  }
  doSomething() {
    console.log('Doing something...');
  }
  handleClick() {
    // since we're using `this` in the callback...
    this.doSomething();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click me</button>
      </div>
    );
  }
}

You need to be using create-react-app (babel) to do it this way. Keep in mind this is not yet a standard but considered experimental syntax:

class Button extends Component {
  constructor(props) {
    super(props);
  }
  doSomething() {
    console.log('Doing something...');
  }
  // must be using babel to do it this way:
  handleClick = () => {
    this.doSomething();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click me</button>
      </div>
    );
  }
}

Just to be clear, if you're not referencing this in the callback, you don't need to worry about binding.

class Button extends Component {
  constructor(props) {
    super(props);
  }
  handleClick() {
    // we're NOT using `this` in the callback...
    console.log('Doing something...');
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>Click me</button>
      </div>
    );
  }
}

You can also do inline binding, but this is considered less ideal in that a new function is created with every click event. It looks like this:

<button onClick={this.handleClick.bind(this)}>Click me</button>

In a function component, you don't need to worry about this:

function Button() {

  const handleClick = () => {
    console.log('Doing something...');
  }

  return (
    <div>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

Passing arguments to handlers

If you need to pass arguments to the handler, one quick and dirty way is to use the last inline binding style from above:

<button onClick={this.handleClick.bind(this, arg1, arg2)}>Click me</button>

If the button (or whatever) were inside a child component, we could pass the whole function as a prop then reference the prop in the child's event attribute:

ParentComponent.js

<ChildComponent handleClick={this.handleChildClick.bind(this, 'tomato')}/>

ChildComponent.js

<button onClick={this.props.handleClick}>Click me</button>

BUT, as mentioned above, inline binding isn't ideal for performance reasons. It's too bad, because the preferred way is a little more lengthy. Basically you have to create a new function that calls the function with the arguments so:

class TestComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {color: 'tomato'};
    this.handleSetColor1 = this.handleSetColor1.bind(this);
    this.handleSetColor2 = this.handleSetColor2.bind(this);
  }
  setColor(color) {
    this.setState({color: color});
  }
  handleSetColor1() {
    this.setColor('aquamarine');
  }
  handleSetColor2() {
    this.setColor('teal');
  }
  render() {
    const styles = {backgroundColor: this.state.color};
    return (
      <div className="TestComponent" style={styles}>
        <button onClick={this.handleSetColor1}>Click me</button>
        <button onClick={this.handleSetColor2}>Click me</button>
      </div>
    );
  }
}

If the event was in the child, it would be the same idea:

ParentComponent.js

class TestComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {color: 'tomato'};
    this.setColor = this.setColor.bind(this);
  }
  setColor(color) {
    this.setState({color: color});
  }
  render() {
    const styles = {backgroundColor: this.state.color};
    return (
      <div className="TestComponent" style={styles}>
        <TestChildComponent setColor={this.setColor} />
      </div>
    );
  }
}

ChildComponent.js

class TestChildComponent extends Component {
  constructor(props) {
    super(props);
    this.handleSetColor = this.handleSetColor.bind(this);
  }
  handleSetColor() {
    this.props.setColor('honeydew');
  }
  render() {
    return (
      <div className="TestChildComponent">
        <button onClick={this.handleSetColor}>Click me</button>
      </div>
    );
  }
}

It's no wonder people prefer using inline binding despite the performance penalty.

Functional components

Regular event handler:

function TestComponent() {
  const [color, setColor] = useState('fff');
  const styles = {backgroundColor: color};

  const handleSetColor = () => {
    setColor('tomato');
  };

  return (
    <div className="TestComponent" style={styles}>
      <button onClick={handleSetColor}>Click me</button>
    </div>
  );
}

Inline event handler:

function TestComponent() {
  const [color, setColor] = useState('fff');
  const styles = {backgroundColor: color};

  const handleSetColor = (e, color) => {
    setColor(color);
  };

  return (
    <div className="TestComponent" style={styles}>
      <button onClick={ e => handleSetColor(e, 'green')}>Click me</button>
    </div>
  );
}

Callback event handler:

function TestChildComponent() {
  // Passed from parent component
  const {color, setColor} = props;
  const styles = {backgroundColor: color};

  const handleSetColor = () => {
    setColor('purple');
  };

  return (
    <div className="TestComponent" style={styles}>
      <button onClick={handleSetColor}>Click me</button>
    </div>
  );
}

Naming event handlers

Know that React doesn't care how your event handling functions are named but a recommended approach for consistency reasons is this:

  • name the parent function according to the action it performs e.g. removeThing, setThing, updateThing, etc.

  • name the matching child's event handler the same thing prefixed with handle e.g. handleRemoveThing, handleSetThing, handleUpdateThing, etc.

Eventually we should get used to seeing the same patterns in our child components:

handleUpdateThing() {
  updateThing(arg);
}

stopPropagation

Sometimes you will find that you have events triggering when you don't want them to. For example, the code below uses a component from the react-copy-to-clipboard library. The onCopy event triggers a callback function. If we wanted to have something inside that element that also has an event-related action (e.g. a link), we may want to stop that event from bubbling up. This can be done with stopPropagation():

<CopyToClipboard onCopy={ updateCopied }>
  <div className="ColorChip">

    {/* content */}

    <Link to="/" className="info-more" onClick={e => e.stopPropagation()}>
      More
    </Link>
  </div>
</CopyToClipboard>

Note that in this particular case, without the onClick={e => e.stopPropagation()}, the console will actually give us a warning:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.