forwardRef in React/Drum Machine

Hey all, I’ve hit a wall in my drum machine project when it comes to getting the audio to play correctly. I’m using refs to accomplish this, but the official docs don’t dive too deeply into this, and I have sound playing on the click event, but only with the last element. Full repo is here, relevant code is as follows:

App.js:

const ref = React.createRef();

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      display: "Press a key to play the drums!"
    };
  }

  handleClick = (name) => {
    this.setState({ display: name });
    ref.play();
  };

  render() {
    return (
      <div id="drum-machine">
        <Display display={this.state.display} />
        <div className="drum-buttons">
          {drumData.map(button => (
            <Button
              handleClick={this.handleClick}
              display={this.state.display}
              name={button.name}
              drumKey={button.key}
              src={button.src}
              ref={ref}
            />
          ))}
        </div>
      </div>
    );
  }
}

and the Button.js file:

const Button = React.forwardRef(({ handleClick, name, drumKey, src }, ref ) => {

  return (
    <div onClick={() => handleClick(name, drumKey, ref)} className="drum-pad">
      {drumKey}
      <audio
      src={src}
      className="clip"
      id={drumKey}
      ref={ref}/>
    </div>
  );
});

Basically, I don’t know precisely what to pass into and then initialize in the handleClick function to target the correct node every time. Any help would be deeply appreciated!

You need to create separate ref for each button.

And also I would rethink your chosen approach.

Telling me to “rethink my chosen approach” is maddeningly vague. Would you care to elaborate on that?

Actually now that I think about that, it’s FCC tests that should change the approach.

Their requirement to have a particular elements with particular ids/classes in particular structure forces campers to do some interesting stuff.

Tests shouldn’t test implementation.

I mean, yes, ids have to show up in these projects far more than they do appear (or should appear) in real world code.

Tests have to test something. There couldn’t possibly be a standard test suite that tested every possible UI to every single drum machine. Keep in mind that the test suite doesn’t have to pass if you have a working drum machine. You can still submit it and call it done. The ultimate criterion to satisfy is the honor system of academic honesty (that it’s your own work), and not the automated test system.

If you really need the bars to go green when you have a fully functional app, you can always put in a bunch of dummy elements with the expected ids, and not display them.

1 Like

I was more referring to forced use of audio tag which together with React requires workarounds (like document.querySelector in the fcc example or stuff like refs).

Also, If I may add, putting aside the issue of separate audio elements in each button for a moment, the way you are using Refs even for just one audio element is not correct.

  1. name your audio element and assign it to this of the class in the return of your render function
ref={this.myAudio}
  1. Inside your constructor, assign your newly named component, right now you have the createRef() method of react outside of the class, you need this in the constructor
 constructor(props) {
   super(props);

    this.myAudio = React.createRef();
 }

Now you have access to myAudio anywhere in your class component with this.myAudio.current and not in the public namespace.

However, this is well and good when it comes to just one ref component, but the tricky part is how to duplicate this for multiple ref components by mapping over all your buttons, for this you are probably better off using the other react style of using refs, that is call back refs,

if I did the example above as a call back ref it would look something like

ref={(input) => this.myAudio = input}

now this.myAudio would be available everywhere in the class component, without using the createRef() in the constructor.
You should now be able to map over all your buttons and create duplicate but unique audio element references with a slight modification to the above,

1 Like

Forced use, as opposed to what? If I’m reading the instructions correctly, the expectation is that there’s an audio element in each tag.

This is a good point. I think I’ll go this route and make sure the app is displaying expected behavior and worry less about arbitrarily adhering to every little facet of the user stories.

Thanks, this is a helpful point to work from!

As opposed to using JS:

new Audio("link/to/audio/file").play()

You can do it without audio tags, but you won’t be able to pass “tests”.

1 Like

Thanks, that is much more explicit!