Binding to event handler that calls setState in ComponentDidMount produces warning

- 1 answer

Ad

I'm using jQuery to create event bindings in a ReactJS component's componentDidMount function, which seems like the right place to do this.

$('body').on('defaultSearchContext.registerQueryEditor', (function(_this) {
  return function(event, component) {
    _this.setState({
      queryEditors: _this.state.queryEditors.concat([component])
    });
  };
})(this));

This code isn't actually run on componentDidMount, it's simply setting up the binding that later calls setState when the event fires. However, this generates the following warning every time this event triggers, which pollutes my console with dozens of warnings:

Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.

I have tried moving the setState code to a separate function like onEvent and calling that from the binding in componentDidMount but the warning is still produced.

Ideally, I'd like to create the binding in the proper place, indeed, there is some issue with doing it in componentDidMount. If not, I'd like to know if it's possible to silence the warning, or whether I should perhaps file a bug for ReactJS itself. If it helps, I'm using ReactJS 0.14.3 (latest at this time).

This is similar to, but not the same as React Js onClick inside render. In that case, the solution is to return an anonymous function to onClick, but that doesn't seem applicable to my situation.

Ad

Answer

Ad

You are trying to coordinate events between independent components. This is a fairly standard thing to do, and it doesn't require DOM events. The standard practice for doing this in React is to use a store/dispatcher pattern like Redux or Flux (I personally prefer redux). However, if this is part of a larger, not-completely-React application, then this may not be possible. If it is just for a small piece of an React app, it may still be overkill.

All you need is an object to coordinate events. An event is just a collection of callbacks, possibly typed or keyed. This requires nothing more than an object shared between two places. DOM Events are overkill; jQuery is overkill. You just need to trigger a callback.

This is a VERY SIMPLE event coordinator.

let simpleEventCoordinator = {
  callbacks: new Map(),
  getHandler(eventKey) {
    let handler = this.callbacks.get(eventKey);
    if (!handler) {
      handler = new Set();
      this.callbacks.set(eventKey, handler);
    }
    return handler;
  },
  registerCallback(eventKey, callback) {
    this.getHandler(eventKey).add(callback);    
  },
  removeCallback(eventKey, callback) {
    this.getHandler(eventKey).delete(callback);
  },
  trigger(eventKey, data) {
    this.getHandler(eventKey).forEach(c => c(data));
  }

Keep a map of callbacks, which will be nameOfEvent => callback(). Call them when asked. Pretty straightforward.

I know nothing about how your components are structured, but you said they are independent. Let's say they look like this:

React.render((
  <div>
    <QueryManager />
    <button onClick={() => simpleEvent.trigger('event')}>{'Update'}</button>
  </div>
), document.body);

This is all your component needs to handle this event

  componentDidMount() {
    simpleEvent.registerCallback('event', this.update);
  }
  componentWillUnmount() {
    simpleEvent.removeCallback('event', this.update);
  }
  update() {
    //do some stuff
  }

I've put together a very simplecodepen demonstrating this.

Ad
source: stackoverflow.com
Ad