94

I'm making and listening for normal DOM CustomEvents to communicate to parent nodes:

In child:

  var moveEvent = new CustomEvent('the-graph-group-move', { 
    detail: {
      nodes: this.props.nodes,
      x: deltaX,
      y: deltaY
    },
    bubbles: true
  });
  this.getDOMNode().dispatchEvent(moveEvent);

In parent:

componentDidMount: function () {
  this.getDOMNode().addEventListener("the-graph-group-move", this.moveGroup);
},

This works, but is there a React-specific way that would be better?

Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51
forresto
  • 12,078
  • 7
  • 45
  • 64
  • 8
    The React way would be to pass callbacks down to children explicitly via props — ``. There's no support for custom events w/ bubbling in React. – andreypopp Feb 22 '14 at 12:00
  • 18
    So, bubble the callbacks down instead of events up? Seems reasonable. – forresto Feb 23 '14 at 08:24
  • 30
    @forresto love the sarcasm, +1 – Dimitar Christoff Apr 14 '14 at 16:56
  • 15
    I wasn't being sarcastic. – forresto Apr 15 '14 at 07:33
  • 5
    it's one thing setting a best practice, another altogether to prevent a viable pattern. such stark contrast to say, http://twitter.github.io/flight/ - which uses the DOMEvents to bubble and propagate synthetic events. – Dimitar Christoff Apr 15 '14 at 08:21
  • 4
    Having adored Microsoft's MVVM in WPF, this was a natural thing I would expect from React - instead of either passing callback through hierarchy of components that don't care about it, or having one allmighty singleton dispatcher (which reeks of bad design for miles). This might be unsupported scenario, but I'll still try how far it will be able to get me :). – Tomáš Kafka Nov 03 '14 at 16:28
  • Your question actually saved my ass. I can't belive React won't have the most performanced feature of the browsers ( event bubling ) – aemonge Apr 30 '15 at 14:18
  • 2
    @TomášKafka I agree, I think that's the reason a lot of us are wanting to bubble up our own custom events so that non-global non-singleton dispatchers at some parent level can handle them. Passing callbacks down is a perfectly viable pattern, but it takes more boilerplate code to pass them down through every level than to have events bubble up and only need to add code to handle them on level(s) that actually do something with the events. – Andy May 10 '15 at 22:04
  • @aemonge Of course, if you're supporting IE9+ you can use custom DOM events. What I want (but haven't been able to find) is a way to get the parent React element of a child, rather than the actual DOM nodes, so that I can roll a similar thing for a project where I'm required to support IE8. – Andy May 10 '15 at 22:06
  • 1
    While I appreciate the 'explicit is better than implicit' approach of each component in the chain passing callback functions (say from great-grandparent component to leaf component), I can see this leading to bugs where a refactor adds another component to the chain, but forgets to pass the function. The flux implementation recommendation from Facebook seems to sidestep this problem with a singleton dispatcher at the root of the "flux tree" that events are passed directly to, but I agree that this does not feel like good design and seems to negate the modularity that React strives for. – Michael Bylstra Aug 09 '15 at 04:41
  • 1
    @aemonge as I demonstrated in my answer, it's not actually difficult to implement your own event bubbling using React context. – Andy Jun 11 '19 at 18:09
  • 1
    @andy you should get the "necromancer" StackOverflow badge, hehhehe. Anyways, indeed there's now lots of native JS functions implemented to solve this ;) – aemonge Jun 12 '19 at 08:50
  • @andreypopp this is very bad, because if the callback try to update the state, you will receive the following warning: "Cannot update a component from inside the function body of a different component.". – AGPX Jun 17 '20 at 11:07

6 Answers6

54

As noted above:

The React way would be to pass callbacks down to children explicitly via props — . There's no support for custom events w/ bubbling in React.

The reactive programming abstraction is orthogonal:

Programming interactive systems by means of the observer pattern is hard and error-prone yet is still the implementation standard in many production environments. We present an approach to gradually deprecate observers in favor of reactive programming abstractions. Several library layers help programmers to smoothly migrate existing code from callbacks to a more declarative programming model.

The React philosophy is based on the Command pattern instead:

enter image description here

References

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
  • 9
    I'm interested in why there's no support for custom synthetic events in React. Is it simply that they were never implemented, or is there a valid design decision why they weren't?. Are events considered harmful like goto statements? – Michael Bylstra Aug 09 '15 at 04:46
  • 6
    @MichaelBylstra Custom events are frowned upon due to a [classic issue](https://lhorie.github.io/mithril/comparison.html): `a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events`. The [command pattern](https://cloud.githubusercontent.com/assets/364727/7505705/054e80f2-f413-11e4-95fa-0b432f1ba262.png) with [one-way data flow](http://www.ibm.com/developerworks/library/wa-react-intro/) are the React philosophy. – Paul Sweatte Aug 13 '15 at 18:13
  • 4
    Considering the fact that components communicate with the Datastore injected by an ancestor via React context, (Redux, React Router, etc. all use context), it's totally bogus to say that a child calling back to an ancestor via whatever function on context is not idiomatic React. There is no actual difference between calling Redux "dispatch" with an "action" object, and calling an "event handler" on context with an "event" object. – Andy Jun 11 '19 at 18:05
  • 2
    Long chains of events triggering other events are quite common in the way some people use Redux (e.g. the redux-saga madness) – Andy Jun 11 '19 at 18:07
  • I get the point, but I have a question. Let's assume that one would build a UI-Kit library which is aimed to work seamlessly across multiple architectures. Some components may need to dispatch custom events because we can assume nothing about an unknown architecture. The strict assumption made by React results in a big issue. You can check the issue here (https://custom-elements-everywhere.com/): React is the sole library that cannot handle properly custom events. Maybe, I miss something and any suggestion would be very appreciated. – 7uc4 Nov 25 '19 at 09:34
  • 2
    "Programming interactive systems by means of the observer pattern is hard and error-prone" - lol. Just speaks about the programming skill of the people who invented React. No observer pattern is a terrible system that produces a lot of code and actually can introduce many more erros – Lenka Pitonakova Apr 02 '20 at 18:52
  • 1
    yeah no respect. "CUSTOM EVENTS BAD" lol right.... they have their place and maybe I like them sometimes. Not having the ability to use them oob is just cultist dogmatism – Toskan Jul 27 '20 at 04:13
9

you can write a simple service and then use it

/** eventsService */
module.exports = {
  callbacks: {},

  /**
   * @param {string} eventName
   * @param {*} data
   */
  triggerEvent(eventName, data = null) {
    if (this.callbacks[eventName]) {
      Object.keys(this.callbacks[eventName]).forEach((id) => {
        this.callbacks[eventName][id](data);
      });
    }
  },

  /**
   * @param {string} eventName name of event
   * @param {string} id callback identifier
   * @param {Function} callback
   */
  listenEvent(eventName, id, callback) {
    this.callbacks[eventName][id] = callback;
  },

  /**
   * @param {string} eventName name of event
   * @param {string} id callback identifier
   */
  unlistenEvent(eventName, id) {
    delete this.callbacks[eventName][id];
  },
};

example (same for triggering)

import eventsService from '../../../../services/events';
export default class FooterMenu extends Component {
  componentWillMount() {
    eventsService
      .listenEvent('cart', 'footer', this.cartUpdatedListener.bind(this));
  }

  componentWillUnmount() {
    eventsService
      .unlistenEvent('cart', 'footer');
  }

  cartUpdatedListener() {
    console.log('cart updated');
  }
}
Dr. Lemon Tea
  • 549
  • 4
  • 13
  • I would agree that an EventAggregator/EventHub is a good solution. To support your answer, I would recommend something like the following: `class Bus extends EventTarget {...}` where "`...`" implements _The Publish-Subscribe_ pattern/interface (`publish()`, `subscribe()`, `unsubscribe()`) which simply wraps `super.dispatchEvent()`, `super.addEventListener()`, and `super.removeEventListener()` (respectively). – Cody Apr 06 '21 at 22:35
6

You could bubble events up through callbacks passed down via contexts: [CodePen]

import * as React from 'react';

const MyEventContext = React.createContext(() => {});

const MyEventBubbleContext = ({children, onMyEvent}) => {
  const bubbleEvent = React.useContext(MyEventContext);
  const handleMyEvent = React.useCallback((...args) => {
    // stop propagation if handler returns false
    if (onMyEvent(...args) !== false) {
      // bubble the event
      bubbleEvent(...args);
    }
  }, [onMyEvent]);
  return (
    <MyEventContext.Provider value={handleMyEvent}>
      {children}
    </MyEventContext.Provider>
  );
};

const MyComponent = () => (
  <MyEventBubbleContext onMyEvent={e => console.log('grandparent got event: ', e)}>
    <MyEventBubbleContext onMyEvent={e => console.log('parent got event: ', e)}>
      <MyEventContext.Consumer>
        {onMyEvent => <button onClick={onMyEvent}>Click me</button>}
      </MyEventContext.Consumer>
    </MyEventBubbleContext>
  </MyEventBubbleContext>
);

export default MyComponent;
Andy
  • 7,885
  • 5
  • 55
  • 61
3

There is another one I found which is quite reasonable as well especially if drilling holes from parent to child to child becomes cumbersome already. He called it less simple communication. Here's the link:

https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/04-less-simple-communication.md

index
  • 3,697
  • 7
  • 36
  • 55
  • This is basically saying "implement the Observer pattern". I agree with Michael Bylstra's comment on the OP, describing the problem noted in Less Simple Communication as "drilling holes"...without bubbling, passing callbacks doesn't handle > 1 level deep structures. – ericsoco Nov 19 '15 at 00:04
3

A possible solution, if you absolutely must resort to the Observer pattern in a ReactJs app you can hijack a normal event. For example, if you want the delete key to cause a <div> that is marked for deletion, you could have the <div> listen for a keydown event which will be invoked by a customEvent. Trap the keydown on the body and dispatch a customEvent keydown event on the selected <div>. Sharing in case it helps someone.

Doug
  • 2,242
  • 2
  • 20
  • 16
3

I realize this question is quite old by now, but this answer might still help someone. I've written a JSX pragma for React that adds declarative custom event: jsx-native-events.

Basically you just use the onEvent<EventName> pattern to watch for events.

<some-custom-element onEventSomeEvent={ callback }></some-custom-element>
Caleb Williams
  • 1,035
  • 5
  • 12