DeltaFlyer
DeltaFlyer

Reputation: 471

React: Pass global keydown event listener down to mapped components

I'm building a drum machine and I'd like allow the user to interact with the buttons of the act via keyboard input. The buttons are rendered via mapping out props from an array of objects containing what becomes props in the react app. Currently there is a state property that changes in the container component that shows which keyboard key was pressed. How do I pass that state property (or event information) down to the mapped components from the container component to the mapped components, and how do I get the correct button to play?

Here's the code:

Container.js

class Container extends React.Component {
  constructor(props) {
    super(props);

    this.handlePowerState = this.handlePowerState.bind(this);
    this.state = {
      sound: "press a button!",
      powered: true,
      lastPressedKey: null
    };
  }

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

  componentWillUnmount() {
    document.removeEventListener("keydown", this.handleKeyPress);
  }

  handleKeyPress = event => {
    console.log(event.key);

    this.setState({ lastPressedKey: event.key });
  };

  /*   keyboardPlaySound = e => {
    console.log(e.key);
    console.log(this.state.lastKeyPressed);
    if (this.props.keyPressed === this.props.keyLetter) {
      this.props.isPowered && this.audio.play();
      this.audio.currentTime = 0;
    }
  }; */

  logTheSound = clipName => {
    this.setState({
      sound: clipName
    });
    //console.log(this.state.sound);
  };

  handlePowerState() {
    this.setState({
      powered: !this.state.powered
    });
  }

  componentDidUpdate() {
    document.addEventListener("keydown", this.keyboardPlaySound);
  }

  render() {
    const ButtonComponents = SoundData.map(button => (
      <SoundButton
        //onClick={this.playSound}
        key={button.keyLetter}
        lastPressedKey={event => this.state.lastPressedKey(event)}
        onKeyDown={this.handleKeypress}
        isPowered={this.state.powered}
        keyLetter={button.keyLetter}
        button={button.togglePlay}
        clipSrc={button.clipSrc}
        clipName={button.clipName}
        //clip={this.state.sound}
        logSound={this.logTheSound}
        tabIndexValue={this.tabIndexValue}
      >
        {button.clipName} {button.key}
      </SoundButton>
    ));
    return (
      <div className="machine-container">
        <main className="button-container">{ButtonComponents}</main>
        <div className="mutation-container">
          <PowerSwitch
            isPowered={this.state.powered}
            onChange={this.handlePowerState}
          />
        </div>
      </div>
    );
  }
}

export default Container;

SoundButton.js

class SoundButton extends React.Component {
  audio = new Audio(this.props.clipSrc);
  //console.log(this.mappedSoundObjects);

  playSound = event => {
    this.props.isPowered && this.audio.play();
    this.audio.currentTime = 0;

    //console.log(this.props.clipName);
  };

  keyboardPlaySound = e => {
    //console.log(e.key);
    console.log(this.props.onKeyDown);
    this.props.isPowered && this.audio.play();
    this.audio.currentTime = 0;
  };

  render() {
    return (
      <button
        //style={buttonBottoms}
        isPowered={this.props.isPowered}
        onClick={event => {
          this.playSound();
          this.props.logSound(this.props.clipName);
        }}
        //onKeyDown={this.handleKeyPress}
        onKeyDown={event => {
          this.keyboardPlaySound();
          this.props.logSound(this.props.clipName);
        }}
        tabIndex={this.tabIndexValue}
        //className="clip myButton ui button"
        className="buttons"
      >
        {this.props.keyLetter}
      </button>
    );
  }
}

export default SoundButton;

As seen in the code, I am using the mount/Unmount pattern as seen here, and I've tried countless ways to try and have a conditional route the event handler to the correct button. The best I've gotten with all of these attempts is to have a sound play after an initial click on a button using all of the keys on the keyboard (any keyboard key will play the same sound that was initially clicked on). Again, how do I route the event handler in the container component to the correct mapped button component?

Upvotes: 0

Views: 401

Answers (1)

leguminator
leguminator

Reputation: 184

Maybe you should consider some kind of notifications between components. Have a look at this : it shows an implementation of a component sending messages which can be subscribed to. https://jasonwatmore.com/post/2019/02/13/react-rxjs-communicating-between-components-with-observable-subject

Upvotes: 1

Related Questions