1

I'm about to build up a keyboard for a piano and I want to use state in order handel the pressed and non pressed keys (keys that finally generate the sound, so that I can play single notes and chords)

I modified an online tutorial the following way: First of all I created these objects:

export const NOTES = [
  { name: "c", accid: "natural", hz: 3270, keyboard: "q" },
  { name: "d", accid: "flat", hz: 3465, keyboard: "2" },
  { name: "d", accid: "natural", hz: 3671, keyboard: "w" },
  { name: "e", accid: "flat", hz: 3889, keyboard: "3" },
  { name: "e", accid: "natural", hz: 4120, keyboard: "e" },
  { name: "f", accid: "natural", hz: 4365, keyboard: "r" },
  { name: "g", accid: "flat", hz: 4625, keyboard: "5" },
  { name: "g", accid: "natural", hz: 4900, keyboard: "t" },
  { name: "a", accid: "flat", hz: 5191, keyboard: "6" },
  { name: "a", accid: "natural", hz: 5500, keyboard: "z" },
  { name: "b", accid: "flat", hz: 5827, keyboard: "7" },
  { name: "b", accid: "natural", hz: 6174, keyboard: "u" },
];

My first step is to load the pressed keyboard keys into an array that is hooked up to the global state object. Later on I want to create a sound for all the objects represented by the keyboard keys which are represented in that state array. But first things first.

Why is this.state.pressedKeys (spread operator) undefined, when I press a key?

TypeError: Cannot read property 'pressedKeys' of undefined

import React from "react";
import Key from "./Key";
import "./Piano.css";
import { NOTES } from "../global/constants";

class Piano extends React.Component {
  constructor(props) {
    super(props);
    this.state = { pressedKeys: [] };
  }

  componentDidMount = () => {
    window.addEventListener("keydown", this.handleKeyDown);
  //  window.addEventListener("keyup", this.handleKeyUp);
  };

  handleKeyDown(event) {
    if (event.repeat) {
      return;
    }

    const key = event.key;
    const updatedPressedKeys = [...this.state.pressedKeys];
    if (!updatedPressedKeys.includes(key)) {
      updatedPressedKeys.push(key);
    }

    this.setState({
      pressedKeys: updatedPressedKeys,
    });
  }

  render() {
    const keys = NOTES.map((note) => {
      return (
        <Key
          key={note.hz}
          note={note.name}
          accid={note.accid}
          pressedKeys={this.state.pressedKeys}
        />
      );
    });

    return <div className="piano">{keys}</div>;
  }
}

export default Piano;

1 Answers1

2

You've missed binding the this of the class component to the handleKeyDown callback, so this isn't referring to the component. This results in this.state being undefined in the callback.

In order of preference:

  1. You can bind using arrow function.

    handleKeyDown = event => { .... }
    
  2. You can bind in the constructor.

    constructor(props) {
      super(props);
      this.state = { pressedKeys: [] };
    
      this.handleKeyDown = this.handleKeyDown.bind(this);
    }
    
  3. You can bind when attaching/passing prop.

    window.addEventListener("keydown", this.handleKeyDown.bind(this));
    

I suggest also using a functional state update to compute if key is included and create the next state if necessary.

handleKeyDown = event => {
  if (event.repeat) {
    return;
  }

  const { key } = event;

  this.setState(prevState => {
    if (prevState.pressedKeys.includes(key)) {
      return prevState;
    }
    return {
      pressedKeys: [...prevState.pressedKeys, key],
    }
  });
}
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Ok, handelKeyUp also works, also by using a functional state update. I use state in order to tggle the keys of the keyboard. Should I map the keyboard keys to my NOTES array, or would it be btter to load the complete array objects into state: { name: "c", accid: "natural", hz: 3270, keyboard: "q" },... – user1953346 Aug 14 '21 at 20:44
  • @user1953346 If you meant mapping the `NOTES` to an array including keypress handlers, I think that would be ok. Since the `NOTES` array itself is "static" I don't think it makes sense to store it in state unless I'm missing *some* aspect of you wanting to update any of that data. – Drew Reese Aug 15 '21 at 19:58
  • Ok fair enough, I can give out colored keys now, thanks. There is one strange behaviour, the handelKeyDown() function above stops after 3 or 4 pressed keys. Principally I should be able to press 12 keys at the same time. This problem must be independent from handleKeyUp since I press all the keys without releasing them – user1953346 Aug 17 '21 at 22:15
  • @user1953346 Is this possibly a limit of concurrent key presses your keyboard can handle/allows? – Drew Reese Aug 17 '21 at 23:23
  • It seems to be like that, I would need a gaming keyboard or a piano with a midi interface, but that's another topic, thanks! – user1953346 Aug 18 '21 at 21:14