97

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?

13
  • 8
    The React way would be to pass callbacks down to children explicitly via props — <Child onCustomEvent={this.handleCustomEvent} />. There's no support for custom events w/ bubbling in React.
    – andreypopp
    Commented Feb 22, 2014 at 12:00
  • 18
    So, bubble the callbacks down instead of events up? Seems reasonable.
    – forresto
    Commented Feb 23, 2014 at 8:24
  • 30
    @forresto love the sarcasm, +1 Commented Apr 14, 2014 at 16:56
  • 15
    I wasn't being sarcastic.
    – forresto
    Commented Apr 15, 2014 at 7:33
  • 5
    it's one thing setting a best practice, another altogether to prevent a viable pattern. such stark contrast to say, twitter.github.io/flight - which uses the DOMEvents to bubble and propagate synthetic events. Commented Apr 15, 2014 at 8:21

6 Answers 6

56

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

7
  • 10
    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? Commented Aug 9, 2015 at 4:46
  • 7
    @MichaelBylstra Custom events are frowned upon due to a classic issue: 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 with one-way data flow are the React philosophy. Commented Aug 13, 2015 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
    Commented Jun 11, 2019 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
    Commented Jun 11, 2019 at 18:07
  • 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 Commented Apr 2, 2020 at 18:52
10

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');
  }
}
1
  • 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
    Commented Apr 6, 2021 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;
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

1
  • 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
    Commented Nov 19, 2015 at 0: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.

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>

Not the answer you're looking for? Browse other questions tagged or ask your own question.