React Drum Machine onKeyPress issue

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;

1 Like

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

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.

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.

5 Likes

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.

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?

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.

Hi Nick,
Why don’t you try if e.keyCode is equall with the letter you press with charCodeAt() in your onKeyPress method

Happy coding man :wink:

1 Like

I’m looking at this thread a few months later. Could you point me somewhere (or just tell me why) using componentWillMount(), a lifecycle component, works for this?