Skip to content

React Events

sergio edited this page Jan 18, 2021 · 24 revisions
index

Intro

React makes use of basic HTML events by wrapping them in something called SyntheticEvents. This wrapper allows React to make sure events are handled the same way across all browsers by a consistent API.

class Tickler extends React.Component {

  tickle = () => {
    console.log('Tee hee!')
  }

  render() {
    return (
      <button onClick={this.tickle}>Tickle me!</button>
    )
  }
}

The handler name is always comprised of on, and the event name itself (i.e. click). These are joined together and camel-cased. As you know, the value of the events are callbacks (either a reference to a function or an inline function). You will commonly see React component methods defined with arrow functions. This is because we often want to access the this keyword within the methods themselves.

component with multiple events:

render() {
    return (
      <canvas 
        onMouseMove={this.handleMouseMove}
        onClick={() => {toggleCycling()}}
        onKeyDown={this.handleKeyDown}
        width='900'
        height='600
        tabIndex="0">
      </canvas>
    )
  }

Handling mouse coordinates with onMouseMove

handleMouseMove = (event) => {
    drawChromeBoiAtCoords(event.clientX, event.clientY);
  }

State

props are dificult to change without recreating the component passing from the Parent giving the props again. For this reason for changing features in the componenet we use this.state. State is for values that are expected to change.

class MyComp extends React.Component {
 
  // we use the constructor to set the INITIAL STATE
  constructor() {
    super()
    this.state = {
      count: 0
    }
  }

  // our increment method makes use of the 'setState' method, which is what we use to alter state
  increment = () => {
    const newCount = this.state.count + 1
    this.setState({
      count: newCount
    })
  }

  render() {
    return (
      <div onClick={this.increment}>
        {this.state.count}
      </div>
    )
  }
}

We set initial state in the constructor because it runs first for React Component Lifecycle reasons.

setState()

setState() sets state asynchronously.

class App extends Component {

  constructor() {
    super()
    this.state = {
      count: 0
    }
  }

  increment = () => {
    console.log(`before setState: ${this.state.count}`)

    this.setState({
      count: this.state.count + 1
    })

    console.log(`after setState: ${this.state.count}`)
  }

  render() {
    return (
      <div onClick={this.increment}>
        {this.state.count}
      </div>
    )
  }
}

// click
//=> before setState: 0
//=> after setState: 0)
// <div>1</div>

"After setState:" should have given 1 instead returned 0. Because of asyncronicity. While component state is a very powerful feature, it should be used as sparingly as possible. State adds (sometimes unnecessary) complexity and can be very easy to lose track of. The more state we introduce in our application, the harder it will be to keep track of all of the changes in our data. Remember: state is for values that are expected to change during the components life.


Using Callbacks to Pass Information

In React, props are used to pass information down the component tree, from parents to children. In order to propagate information in the opposite direction, we can use callback functions, also passed down as props from parent components to children. However, because these functions are defined in the parent, they will still be in that context if called from a child component.

This allows the callback to be owned by a different component than the one invoking it. Once invoked, the callback can effect change in the component that owns it, instead of the component that called it.

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import learnSymbol from './data.js'
import Matrix from './Matrix.js'

ReactDOM.render(<Matrix values={learnSymbol} />, document.getElementById('root'));


//Matrix.js
import React, { Component } from 'react';
import learnSymbol from './data.js'
import Cell from './Cell.js'
import ColorSelector from './ColorSelector.js'

export default class Matrix extends Component {

  constructor() {
    super()
    this.state = {
      selectedColor: '#FFF'
    }
  }

  setSelectedColor = (newColor) => {
    this.setState({
      selectedColor: newColor
    })
  }

  genRow = (vals) => (
    vals.map((val, idx) => <Cell key={idx} color={val}  selectedColor={this.state.selectedColor} />)
  )

  genMatrix = () => (
    this.props.values.map((rowVals, idx) => <div key={idx} className="row">{this.genRow(rowVals)}</div>)
  )

  render() {
    return (
      <div id="app">
        <ColorSelector setSelectedColor={this.setSelectedColor} />
        <div id="matrix">
          {this.genMatrix()}
        </div>
      </div>
    )
  }
}

Matrix.defaultProps = {
  values: learnSymbol
}


//ColorSelector.js
import React, { Component } from 'react';

export default class ColorSelector extends Component {

  makeColorSwatches = () => (
    ["#F00", "#F80", "#FF0", "#0F0", "#00F", "#508", "#90D", "#FFF", "#000"].map((str, idx) => {
      let callback = () => this.props.setSelectedColor(str)
      return <div onClick={callback} key={idx} className="color-swatch" style={{backgroundColor: str}}/>
    })
  )

  render() {
    return (
      <div id="colorSelector">
        {this.makeColorSwatches()}
      </div>
    )
  }
}


//Cell.js
import React, { Component } from 'react';

export default class Cell extends Component {
  
  constructor(props) {
    super(props)
    this.state = {
      color: this.props.color
    }
  }

  handleClick = () => {
    this.setState({
      color: this.props.selectedColor
    })
  }
  
  render() {
    return (
      <div onClick={this.handleClick} className="cell" style={{backgroundColor: this.state.color}}>
      </div>
    )
  }
  
}

Clone this wiki locally