React Drum Machine onKeyPress issue

React Drum Machine onKeyPress issue
0

#1

I’m working on the React Drum Machine project and I’m mostly done. The big problem I am running into is making it so the user can trigger the drum pads sounds via the keyboard.

Here is my drum machine. As you can see it works fine when clicking the buttons, but I can’t trigger the buttons via key press without first clicking said button with the mouse. After you click the button with the mouse then you can trigger it by pressing a key.

Aftering clicking a key, let’s say ‘E’, you can then trigger E by pressing a key. After E is triggered on key press how do I go about resetting state so that it clears and E won’t be triggered again on any key press?

Do I need to use componentDidMount and document.add/remove key listener to do this in React?

Here’s the code:

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      buttonClicked: '',
      name: ''
    }
  }

  handleClick = (e, name) => {
    this.setState({ buttonClicked: e })
    this.setState({ name: name})

    const sound = document.getElementById(e);
    sound.volume = 1;
    sound.play();
  }

  onKeyPress = (event, name) => {
    this.setState({ buttonClicked: event });
    this.setState({ name: name });

    document.getElementById(event);

    const sound = document.getElementById(event);
    sound.play();
  }

  render() {
    return (
      <DrumDisplay
        buttonClicked={this.state.buttonClicked}
        name={this.state.name} 
        handleClick={this.handleClick}
        onKeyPress={this.onKeyPress}
      />
    );
  }
}

export default App;

And here is the functional component that contains the layout:

onst DrumDisplay = (props) => {
  return (
    <div id="drum-machine">
      <h1>React Drum Machine</h1>

      <div className="drum-pad-container">
        <div className="drum-pad" onClick={() => props.handleClick('Q', 'Heater 1')} onKeyPress={ () => props.onKeyPress('Q')} tabIndex="0" >
          <audio className="clip" id="Q">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Heater-1.mp3" type="audio/mp3" />
          </audio>
          <span>Q</span>
        </div>
        <div className="drum-pad" onClick={() => props.handleClick('W', 'Heater 2')} onKeyPress={ () => props.onKeyPress('W')} tabIndex="0">
          <audio className="clip" id="W">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Heater-2.mp3" type="audio/mp3" />
          </audio>
          <span>W</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('E', 'Heater 3')} onKeyPress={ () => props.onKeyPress('E')} tabIndex="0">
          <audio className="clip" id="E">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Heater-3.mp3" type="audio/mp3" />
          </audio>
          <span>E</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('A', 'Heater 4')} onKeyPress={ () => props.onKeyPress('A')} tabIndex="0">
          <audio className="clip" id="A">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Heater-4_1.mp3" type="audio/mp3" />
          </audio>
          <span>A</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('S', 'Clap')} onKeyPress={ () => props.onKeyPress('S')} tabIndex="0">
          <audio className="clip" id="S">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Heater-6.mp3" type="audio/mp3" />
          </audio>
          <span>S</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('D', 'Open HH')} onKeyPress={ () => props.onKeyPress('D')} tabIndex="0">
          <audio className="clip" id="D">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Dsc_Oh.mp3" type="audio/mp3" />
          </audio>
          <span>D</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('Z', 'Kick \'N Hat')} onKeyPress={ () => props.onKeyPress('Z')} tabIndex="0">
          <audio className="clip" id="Z">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Kick_n_Hat.mp3" type="audio/mp3" />
          </audio>
          <span>Z</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('X', 'Kick')} onKeyPress={ () => props.onKeyPress('X')} tabIndex="0">
          <audio className="clip" id="X">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/RP4_KICK_1.mp3" type="audio/mp3" />
          </audio>
          <span>X</span>
        </div>
        <div className="drum-pad"  onClick={() => props.handleClick('C', 'Closed HH')} onKeyPress={ () => props.onKeyPress('C')} tabIndex="0">
          <audio className="clip" id="C">
            <source src="https://s3.amazonaws.com/freecodecamp/drums/Cev_H2.mp3" type="audio/mp3" />
          </audio>
          <span>C</span>
        </div>
    </div>


      <div id="display">
        <h3>Volume</h3>
        <div className="slider">
          <Slider min={0} max={20} defaultValue={3} handle={handle} />
          {console.log(handle)}
        </div>
        <div className="label">
          <span>{props.name}</span>
        </div>
      </div>
    </div>
  );
}

export default DrumDisplay;


#2

This is not a React solution, but it may be helpful.


#3

You really should consider making components for each drum pad. You have a ton of repetitive code here.


#4

Yeah I was considering that. Just leaving it like that for now for the sake of simplicity. Still learning React and breaking things up confuses me.

I wanted to get the key press issues resolved and then once I’m comfortable with the code then go and refactor it and clean it up.


#5

I also tried using onKeyDown until I realized it won’t work unless the div has focus. I switched to using listeners in componentWillMount like this

  componentWillMount(){
    document.addEventListener("keydown", this.handleKeyPress.bind(this));
  }

and it works now.


#6

I guess that makes sense. That’s why the more recently clicked on drum pad would always play regardless of which key was clicked, because it was the one that was still in focus.

Trying to implement your solution but I’m stuck:


class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      buttonClicked: '',
      name: ''
    }
  }

  handleClick = (e, name) => {
    this.setState({ buttonClicked: e })
    this.setState({ name: name})

    const sound = document.getElementById(e);
    sound.volume = 1;
    sound.play();

  }

  onKeyPress = (e, name) => {
    this.setState({ buttonClicked: e });
    this.setState({ name: name });

    const sound = document.getElementById(e);
    sound.play();

  }


  componentWillMount = (e) => {
    document.addEventListener("keydown", this.onKeyPress.bind(this));
  }


  render() {
    return (
      <DrumDisplay
        buttonClicked={this.state.buttonClicked}
        name={this.state.name}
        handleClick={this.handleClick}
        onKeyPress={this.onKeyPress}
      />
    );
  }
}

export default App;

And here is the component for each individual drum pad:

import React, { Component } from 'react';


const DrumPad = (props) => {
  return (
    <div className="drum-pad"
      onClick={() => props.handleClick(props.id, props.soundName)}
      tabIndex="0">

      <audio className="clip" id={props.id}>
        <source src={props.sound} type="audio/mp3" />
      </audio>
      <span>{props.id}</span>

    </div>
  );
}


export default DrumPad;

Any pointers? Not sure how to tie componentWillMount to the onKeyPress function properly.


#7

Still stuck on this issue. Made a StackOverflow post.

Updated/refactored the code and cleaned it up. Any ideas on how to get around this issue?


#8

You have a name parameter in both handleClick and onKeyPress methods, but I do not see where you are passing an extra argument for name via your binding. Also, componentWillMount is being deprecated. Use componentDidMount instead and it does not take any arguments.

What exactly is name supposed to be?

You do realize that e will be the event object you are assigning to buttonClicked state property?

Also, can you repost your latest live site link? The one in the original post shows a 404 error message. It would be great if you have all the source code available on github or somewhere else.


#9

Name is supposed to be the text passed to the element that displays the name of each drum pad i.e. hi-hat, heater, etc…

I fixed the link. It works now. I’ve also abandoned the componentWillMount/document.addEventListener approach and changed some of the code. Here is the repo for the project.