liamdavis
liamdavis

Reputation: 25

How do I bind a mapped onkeypress function to an audio element with React Hooks?

I'm refactoring a previously made class-based React app to be functional with the use of Hooks. It's a simple drum machine app. I'm using .map to populate the page from an array of objects containing the data for reach drum pad and it's associated sound.

{soundBank.map((sound) => (
        <DrumPad
          key={sound.id}
          id={sound.id}
          letter={sound.letter}
          src={sound.src}
          handleDisplay={handleDisplay}
        />
      ))}

After refactoring, everything seems to work except for playing individual drum pads using onKeyPress. Here's what my code looked like as a class-based component:

handleKeyPress = event => {
if (event.keyCode === this.props.letter.charCodeAt()) {
  this.audio.play();
  this.audio.currentTime = 0;
  this.props.handleDisplay(this.props.id);
}

render() {
return (
  <div
    className="drum-pad"
    id={this.props.id}
    onClick={this.handleClick.bind(this)}
    onKeyPress={this.handleKeyPress.bind(this)}
  >
    <h1>{this.props.letter}</h1>
    <h2>{this.props.id}</h2>
    <audio
      className="clip"
      id={this.props.letter}
      src={this.props.src}
      ref={ref => (this.audio = ref)}
    ></audio>
  </div>
);}

Here's how it looks as a functional component:

let audio = useRef(null);

const handleKeyPress = (event) => {
if (event.keyCode === props.letter.charCodeAt()) {
  audio.play();
  audio.currentTime = 0;
  props.handleDisplay(props.id);
}
};

return (
<div
  className="drum-pad"
  id={props.id}
  onClick={handleClick}
  onKeyPress={handleKeyPress}
>
  <h1>{props.letter}</h1>
  <h2>{props.id}</h2>
  <audio
    className="clip"
    id={props.letter}
    src={props.src}
    ref={(ref) => (audio = ref)}
  ></audio>
</div>);

I've tried using useCallback, as well as creating state for audio, but I can't get it to work. When the page is loaded, the first key press plays a sound, but any presses after that, and I get the error:

Uncaught TypeError: Cannot read property 'play' of null
at HTMLDocument.handleKeyPress

Through testing for errors using console.log, it seems that, whenever a key is pressed, the handleKeyPress function is being called 9 times (once for every drum pad). How can I make it so only one specific drum pad gets activated on a single key press, and how can I make sure that audio doesn't revert to null? The issue may also be related to my use of ref. I'm doing this to learn Hooks (and React in general), so any pointers in the right direction would be much appreciated.

SOLUTION

Here is my solution based off of the chosen answer from Oleksandr Kovpashko:

const audio = useRef(null);

const handleKeyPress = useCallback((event) => {
if (event.keyCode === props.letter.charCodeAt()) {
  audio.current.play();
  audio.current.currentTime = 0;
  props.handleDisplay(props.id);
}}, []);

<audio
    className="clip"
    id={props.letter}
    src={props.src}
    ref={audio}
  ></audio>

Upvotes: 0

Views: 329

Answers (2)

HermitCrab
HermitCrab

Reputation: 3274

I see 3 problems:

When you create a ref object using useRef, you can access it by adding .current (for example: audio.current.play())

The charCodeAt function requires an index, maybe this is why handleKeyPress is called several times

Do not call removeEventListener unless you called addEventListener before

Upvotes: 0

Oleksandr Kovpashko
Oleksandr Kovpashko

Reputation: 800

Seems like you use refs in the wrong way.

The useRef hook returns a "ref" object that you should pass directly to the ref prop of the <audio> tag. Then access the audio using audio.current in handleKeyPress function. Also, you should wrap the event handler into useCallback hook because the current implementation gives you a new handleKeyPress function on each render cycle.

PS: Why are you trying to remove the event listener in the returned value from your handleKeyPress function? It's not necessary to manage event listeners manually with React unless you added them manually using addEventListener.

Upvotes: 1

Related Questions