Event handler on parent and child elements, only running parent if child is not called

Using React…

I’ve got BackgroundReactComponent which has an onClick. And a child button which as an onClick handler or is an anchor. (You can’t nest anchors).

When the child button is clicked, the parent onClick is also called…

You can’t call evt.preventDefault() or evt.stopPropogation() as we used to do with jQuery do to the virtual events in React. For more details, see this article:

https://medium.com/@ericclemmons/react-event-preventdefault-78c28c950e46#.wd9q3r2xy

which states:

I would have expected that, since events are turned into SyntheticEvents, the event.preventDefault() and event.stopPropagation() would call console.warn or console.error due to the break in functionality.

So…how do we prevent the parent handler if the child was clicked?

On the parent React component, I add this function:

  targetHasClickHandler(event) {
    let el = event.target;
    while(el) {
      if (el.getAttribute('data-click-handler')) {
        return true;
      }
      el = el.parentElement;
    }
    return false;
  }

On the top level parent, click handler:

 onClick={(evt) => {
            if (this.targetHasClickHandler(evt)) {
              return null;
            }
            // else do something
            window.location = contentUrl;
          }

and on all the child elements that have a clickHandler including any anchor tags:

<SomeComponent 
  data-click-handler="true"
/>

Credit to @robwise for the idea of using data attributes to figure out what to run or not run.

I got some feedback that calling evt.stopPropogation() should work. Here’s an example:

Here’s your example refactored. Note that you shouldn’t use instance methods if they don’t rely on this as they can be made static:

const elOrParentIsForeground = (el) => el && (el.getAttribute('data-is-foreground') || elOrParentIsForeground(el.parentElement));

const MyComponent = () => (
  <Parent onClick={(event) => !elOrParentIsForeground(event.target) && console.log('background clicked!')}>
    <Child data-is-foreground onClick={() => console.log('foreground clicked!')} />
  </Parent>
);

Or, done the other way around:

const isParentClicked = (event) => event.target.getAttribute('data-is-parent');

const MyComponent = () => (
  <Parent data-is-parent onClick={(event) => isParentClicked(event) && console.log('background clicked!')}>
    <Child onClick={() => console.log('foreground clicked!')} />
  </Parent>
);
1 Like

Why? to refactor to SFC if needed?

It’s just not necessary, why are you creating a new method for each instance when its implementation isn’t tied to any specific instance?