Custom events

Custom events
WIP

Say that you’ve got an <input>, and you want to be notified when its value changes. Doing so is simple — you just pass an onChange prop.

Okay, but let’s switch things up a little, and say that instead of using a plain <input> you want to set its styles by wrapping it with a custom Input component. This means that there’s no built in onChange prop.

How will you be notified when the custom input’s value changes?

The Input component can accept its own custom onChange prop! In fact, the prop can be called whatever you like.

function handleChange(value) {
  // ...
}

React.createElement(
  Input,
  { onChange: handleChange }
)

The Input component can then call the passed callback from within the native input event handler — or could even pass the callback directly to the internal input’s onChange prop.

It’s probably easier to see this in action, so I’ve put together a demo of a Timer component. Each time that the displayed time changes, the timer calls its onChange callback — which updates the value of the box at the bottom of the page.

index.js
TimerDisplay.js
styles.css
index.html
import React from 'react'
import ReactDOM from 'react-dom'
import { TimerDisplay } from './TimerDisplay' 

class Timer extends React.Component {
  constructor() {
    super()
    this.state = {
      time: 0,
    }
    this.start = this.start.bind(this)
    this.stop = this.stop.bind(this)
    this.reset = this.reset.bind(this)
  }

  render() {
    return React.createElement(
      TimerDisplay,
      {
        title: 'POMODORO',
        time: this.getTimeRemaining(),
        active: this.state.active,
        onStart: this.start,
        onStop: this.stop,
        onReset: this.reset,
      }
    )
  }
  
  getTimeRemaining() {
     return Math.max((60*25 - this.state.time)/60, 0)
  }
  
  start() {
    this.setState({
      lastTime: Date.now(),
      active: setInterval(() => {
        let now = Date.now()
        let newTime =
          this.state.time +
          (now - this.state.lastTime) / 1000
        this.setState({
          time: newTime,
          lastTime: now
        })
        
        // If the Timer element receives an `onChange` prop, then
        // call it each time the timer's current reading changes.
        if (this.props.onChange) {
          this.props.onChange(this.getTimeRemaining())
        }
      }, 37)
    })
  }
  stop() {
    clearInterval(this.state.active)
    this.setState({
      active: null,
    })
  }
  reset() {
    this.setState({
      time: 0,
    })
    if (this.props.onChange) {
      this.props.onChange(25)
    }
  }
}

ReactDOM.render(
  <Timer onChange={(time) => {
    document.getElementById('latestTime').innerHTML =
      parseInt(time*100)/100
  }} />,
  document.getElementById('root')
)
Build In Progress

#Now it’s your turn

To get the contact details out of your ContactForm element, it’s going to need an add button and a callback prop — let’s call it onAddContact.

When the add button is clicked, the form will need to call the callback prop, with an object containing the current name and email.

I’ve added an event handler that logs the added contact details to the console.

Your task is to add the add button (how meta!), and hook it up to the callback prop.

index.js
styles.css
import PropTypes from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom'
import './styles.css'

class ContactForm extends React.Component {
  constructor() {
    super()
    this.state = {
      name: "",
      email: "",
     }
    this.handleChangeName = this.handleChangeName.bind(this)
    this.handleChangeEmail = this.handleChangeEmail.bind(this)
  }

  render() {
    return (
      <div className='ContactForm'>
        <label>
          <span>Name</span>
          <input
            value={this.state.name}
            onChange={this.handleChangeName}
          />
        </label>
        <label>
          <span>E-mail</span>
          <input
            value={this.state.email}
            onChange={this.handleChangeEmail}
          />
        </label>
      </div>
    )
  }
  
  handleChangeName(event) {
    this.setState({
      name: event.target.value,
    })
  }
  
  handleChangeEmail(event) {
    this.setState({
      email: event.target.value,
    })
  }
}

ContactForm.propTypes = {
  onAddContact: PropTypes.func.isRequired,
}

ReactDOM.render(
  <ContactForm
    onAddContact={(contact) => console.log(contact)}
  />,
  document.getElementById('root')
)
Build In Progress

    As with the previous exercise, this one is something you might see in a real app — so it’s worth giving it a solid try before taking a look at the solution. And if you do need the solution, make sure to finish off your code afterwards!

    And once you’re done, all that’s left is to hook up your form with your list. You’re nearly there!

    Progress to next section.