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>