Help. Does it make sense to pass an event to child components in React?

I’m not sure if this is the correct way to go here, please advise me.

I’m designing the drum machine of the Front End Development Libraries challenge, and the idea I have is to have a ‘global’ onKeyPress event in the most outer div and in some way pass that event to all the drum-pads and to the display (sort of how event delegation in JavaScript works). Then, according to which key is pressed, the corresponding pad would play its clip, and the display would show the string describing the audio clip (unless I’m missing something if I would add an onKeyPress property in every drum-pad, each drum-pad will fire only if in focus when the corresponding key is pressed, right?).
However, I can’t figure out how to pass said global event to all child components. Is it possible? Does this make sense, or should I think of another way to handle this? Thank you

This is what I have so far:

I wouldn’t call that “global”. To me that would be a variable that is by default available everywhere. If I understand you correctly, you are passing it around as a parameter - that is not global. It may be available in a lot of places, but you are passing it as a parameter so it isn’t just magically “there”.

I don’t have time at the moment to go through your code, but if I remember correctly, I too had one on key press event handler. And I assume that I had onClick events on all the pads. I assume these would call a function that would handles making the noise and changing state to cause the text to change.

Thanks for your response.
Well I called it ‘global’ just to try to be clear, but your definition makes more sense: it’s just an event that I want to pass around as a parameter.

This all makes sense, what I can’t figure out is how to pass an onKeyPress event detected in a parent component to a child component:

export default function App() {
  return (
    <div
      className="App"
      tabIndex={-1}
      onKeyPress={(e) => {
        console.log("can I pass this event to DrumPad child components? how?");
      }}
    >
      <h1>fcc drum mAcHine test</h1>
      <DrumPad padLetter="q" />
    </div>
  );
}

One thing that is popping to my mind is to use the event to briefly modify the parent component’s state, pass it as props to DrumPad and use it to trigger the sound, but that seems overly convoluted.

One thing that is popping to my mind is to use the event to briefly modify the parent component’s state, pass it as props to DrumPad and use it to trigger the sound …

That was actually what I was thinking to do. I don’t know, in my project I didn’t pass the key press event down to the children. But yeah, if I wanted to do that, that is exactly how I would do it - the handler for the key press would set the state of that component that would be passed down to the children. I would say that I probably would save/pass the entire event, just the relevant information.

…but that seems overly convoluted.

Maybe a little? But that’s just part of dealing with React. If you want the component to keep track of a “state”, then it gets stored in state.

1 Like

Well, I’ll try that. What really annoys me is that (pardon my probably incorrect terminology) an onClick event set on an outer div component is fired if I click in an inner div component, whereas the same doesn’t happen with an onKeyDown event. That behavior is exactly what I was hoping to get from onKeyDown

Right, I did one onKeyDown for the wrapping div to catch key presses, but used a separate onClick event for each pad.

Hello Marco,

I think it is possible to pass an on key press event down, but I don’t know that you need to.

You set up an onClick event on the drum pad components, which you already have done. Then you can set up an event listener for key press in the parent component.

Inside the function for the key press, you can figure out which key was pressed, if the pressed key was one of the drumpad buttons, you can select it, and then call .click() in order to click that element, which will just pass the work down to your onClick functions.

Example:
Here was my keyPress function, Some details will be different but the idea is there.

  onKeyPress(event) {
    // Monitor key press events. If the keyCode is one of the drumKeys, target that button then click and focus it
    if (drumKeys.hasOwnProperty(event.keyCode)) {
      let button = document.getElementById(drumKeys[event.keyCode])
        .parentElement;
      button.click();
      button.focus();
    }
  }

Hopefully that helps!
Cody

1 Like

@Marco16168 I don’t see why we could want an onClick on the outer div.

Side issue to what you’re talking about, but re what causes the event to fire: you’re always clicking on the outer div when you click on the inner div (it’s physically impossible not to). But you’re not keyboard[ing?] on the outer div if you focus on an inner div using the keyboard and hit a key.

2 Likes

That is what I was missing. Thanks

Hey Cody, thanks for chiming in.
I don’t think I’m following you. In my example I can’t call the function playSound from the parent because it’s defined in the DrumPad child. That’s why I was trying to pass the event.

I think I need a break for today :sweat: :sweat: :sweat:

Yeah, this is tough stuff, especially when you’re learning. Don’t be hard on yourself. I think I struggled with this one too.

1 Like

Take a good look at @codyjamesbrooks example code again. Notice he isn’t calling a function to play the sound, he is “clicking” on the button. In other words, he is triggering a click event on the button, just like you would if you clicked the mouse on the button.

1 Like

Ah! I knew I needed a break :sweat_smile: .
Probably still do, but thanks for pointing that out for me.
Tried that quickly and it works.
Sorry @codyjamesbrooks for not giving you credit earlier :sweat:
I’m curious though: isn’t selecting components in this way, not really the ‘React-way’? Or can we safely do that?

Do you mean getting a reference to a button so you can programmatically click on it? I’m no React expert but I would be very surprised if React has anything to say about this. I could be wrong though, perhaps some of the more experienced React people around here will chime in?

I agree with @bbsmooth. And would love for anyone with more experience to voice their thoughts.

That was the method that I used to solve that particular issue. Whether or not it is the best/“Most React” way, I’m not sure.

I suppose the purest React thinking involves child components managing their own behavior, and a parent component staying concerned with protecting/updating/communicating state to the children.
If a child component is going change state, then that should happen using a callback that is bound to the parent component.

So if you subscribe to that way of thinking, maybe it isn’t the BEST solution. Because you are making the parent do something that the child should be able to do on their own.

Yes, I mean…when we use document.getElementById(), or document.querySelector() we’re referring to actual DOM element and not, strictly speaking, to elements within the React virtual representation of the DOM. I believe that this could cause issues, at least, to my understanding.
Probably not the case here, but in more complex apps, I believe this is to avoid.
I am also curious to hear an opinion from an experienced developer.

1 Like

In this case, you aren’t manipulating the DOM elements, you are just trigger an event on them, so I don’t think this will cause any issues with the React shadow DOM.

Fair point. I was thinking of a scenario in which we trigger an event on an element before that element has an ID assigned from within React, for instance.

Yes, I mean…when we use document.getElementById() , or document.querySelector() we’re referring to actual DOM element and not, strictly speaking, to elements within the React virtual representation of the DOM. I believe that this could cause issues, at least, to my understanding.

It won’t cause issues, but is not recommended. Why? Because it couples your code to the DOM structure and prevents reusability.

For this particular problem it’s not a big deal. But suppose you’re working on a large codebase and want to reuse this component? You can no longer do that, because you’d have to always rewrite the selector logic. Also, if someone changes the selectors your component will break.

Instead, if you want a reference to a DOM element, always use react refs (except for some advanced reason, like side-stepping issues with react synthetic events)

As for your original question about having a “global” event handler, you definitely made a good decision and were half way there. But instead of passing the event down, you just pass the handler. With react, you can pass anything you want as a prop. Even a function.

Spoiler code follows:

export default function App() {
  const keyHandler = sound => {
      sound.currentTime = 0;
      sound.play();
  }

  return (
    <div
      className="App"
      tabIndex={-1}
    >
      <h1>fcc drum mAcHine test</h1>
      <DrumPad padLetter="q" keyHandler={keyHandler} />
      <DrumPad padLetter="w" keyHandler={keyHandler} />
    </div>
  );
}

export default function DrumPad(props) {
  const audioEl = useRef(null);

  return (
    <button
      className="drum-pad"
      onClick={() => props.keyHandler(audioEl.current)}
    >
      {props.padLetter}
      <p>{props.event}</p>
      <audio id={props.padLetter} src={sample} ref={audioEl}></audio>
    </button>
  );
}
1 Like