33

Let's say I have nested components like this:

<root />
  <comp1 />
    <comp2 />
      <target id={this.props.id}>
        <div>click me</div>

I want to make clicking on target run a function on root:

//on root component
this.action = function(id){}

Do I need to do manually set a property on every component in the chain, like in the React tutorial example? Jsfiddle

<root />
  <comp1 clickHandler={this.action}/>
    <comp2 clickHandler={this.clickHandler}/>
      <target id={this.props.id} clickHandler={this.clickHandler} />
        <div onClick={this.props.clickHandler.bind(this, this.props.id)}>click me</div>

Or is there some way to bubble the events up like in normal DOM?

Clarkie
  • 7,490
  • 9
  • 39
  • 53
kapsi
  • 897
  • 1
  • 8
  • 9

3 Answers3

39

React supports Synthetic Events across it's Virtual DOM in both capturing and bubbling phases (as described here: https://facebook.github.io/react/docs/events.html).

This means that you could put an onClick handler on any DOM element near the root and it should trigger for all Click events on the page:

<root>
  <div onClick={this.handleAllClickEvents}>
    <comp1>
      <comp2>
        <target>
          <div id={this.props.id}>click me</div>

However, since it will fire for all click events, you would need to disambiguate between them in the click handler (which makes for some pretty ugly code).

function handleAllClickEvents(event) {
  var target = event.relatedTarget;
  var targetId = target.id;
  switch(targetId) {
    case 'myBtn1':
      // handle myBtn1 click event
    case 'myBtn2':
      // handle myBtn2 click event
  }
}

This style of Event Delegation is actually what React does under the hood (https://shripadk.github.io/react/docs/interactivity-and-dynamic-uis.html#under-the-hood-autobinding-and-event-delegation) so you have to ask yourself if that's what you really want to do.

Alternatively you might want to look at something like the Dispatcher pattern in Flux (https://reactjs.org/blog/2014/07/30/flux-actions-and-the-dispatcher.html). It's a little bit more complicated to get going but it's a better solution overall.

Martoid Prime
  • 2,433
  • 2
  • 11
  • 11
chrismilleruk
  • 2,516
  • 2
  • 16
  • 8
4

You can use the shorthand to pass props through to child components

<Component {...this.props} more="values" />

Transferring props

So in your case:

<root />
  <comp1 clickHandler={this.action}/>
    <comp2 {...this.props} />
      <target {...this.props} id={this.props.id} />
        <div onClick={this.props.clickHandler.bind(this, this.props.id)}>click me</div>
Clarkie
  • 7,490
  • 9
  • 39
  • 53
  • 4
    While {...this.props} works, it makes it really difficult to reason about what's get passed down components, after maintaining a project with hundreds of components this get dizzyingly impossible to track. – Patrick Jul 20 '17 at 12:15
  • @Patrick you can try [Redux](http://redux.js.org/). It introduce a concept `selector`, which allow you select the props from a global data store instead of passing props from parent – sundayku Aug 31 '17 at 16:45
  • Thx, redux is used for global state, plus its not what the question eas about. I just specifically stated that the spread operator is confusing at best when things get nested – Patrick Aug 31 '17 at 16:49
  • 2
    I have not used react very long so take this with a grain of salt but this seems like an anti-pattern to me if you're trying to decouple your components and reuse them. This pattern makes them dependent upon being children of the parent object with specifically named properties. – kcgolden Apr 22 '18 at 11:46
  • 1
    This is smelly, today it has a name - prop drilling, and it is an anti pattern – refaelio Jul 07 '20 at 15:10
1

If you want to distinguish which element was clicked at the end, you may use Event object and find there some interesting stuff, such as DOM element type.

<AnElement>
 <button onClick={e => this.eventHandler(e)}>Click me</button>
 <input type="text" onClick={e => this.eventHandler(e)}>Click me</input>

eventHandler = (e) => {
        console.log(e, e.nativeEvent.toElement.nodeName);
...}

And you get button or input depending on what you've clicked. That's what i looked for for my project.

More over you may find a DOM tree in an array representation array representation

Hope it helps

Alexey Nikonov
  • 4,958
  • 5
  • 39
  • 66