Emitting and handling global events with react

ThomasReggi picture ThomasReggi · Dec 14, 2014 · Viewed 31.3k times · Source

I'm playing around a bit with react to build an "Add to cart button". Here's my code.

var ProductPurchase = React.createClass({
  handleSubmit: function(e){
    e.preventDefault();
    $.ajax({
      url: "/cart/add.js",
      method: "post",
      dataType: "json",
      data: {
        "id": this.props.variantId,
        "quantity": this.props.quantity,
      },
      success: function(data) {
        // emit cart added event
      }.bind(this),
      error: function(xhr, status, err) {
        // emit error event (cart added)
      }.bind(this)
    });
  },
  getDefaultProps: function(){
    return {
      quantity: 1,
      variantId: 231634908,
      buttonText: "Add to cart"
    }
  },
  render: function() {
    return (
      <div className="productPurchase">
        <form action="/cart/add" method="post" enctype="multipart/form-data" onSubmit={this.handleSubmit}>
          <input type="hidden" name="quantity" value={ this.props.quantity } />
          <input type="hidden" name="id" value={ this.props.variantId } />
          <button type="submit">{this.props.buttonText}</button>
        </form>
      </div>
    );
  }
});

What I'm curious about is this ajax handler. I'm pretty sure the whole point of react is interoperability between components, except I don't know where to lead these events off to. I could imagine a couple of different components like a cart count indicator if success or a error alert if failure but I don't exactly know how to tap into these. Is this the whole point of flux's dispatchers?

Answer

Michael Hart picture Michael Hart · Dec 14, 2014

Yes, it's certainly part of the point of Flux's dispatchers – or any event emitter that you wanted to use.

Before you go down that path though, it's very easy to just pass down event handlers as props without using Flux or custom event emitters – just as you would with onSubmit, onClick, etc handlers for normal DOM elements. Then have the parent deal with setting the state, and potentially communicating that to other children (via props).

So in this case, imagine a parent component that deals with the events:

var RootComponent = React.createClass({
  handleCartAdded: function(cart) {
    console.log('Got a new cart: ' + cart);
  }
  handleError: function(err) {
    console.error(err)
  }
  render: function() {
    return (
      <ProductPurchase onCartAdded={this.handleCartAdded} onError={this.handleError} />
    )
  }
})

And then the relevant part of your ProductPurchase component would be:

  success: function(data) {
    this.props.onCartAdded(data)
  }.bind(this),
  error: function(xhr, status, err) {
    this.props.onError(err)
  }.bind(this)

A more complex example would be to pass the result to another child component – but again, leave it up to the parent to manage this:

var RootComponent = React.createClass({
  handleCartAdded: function(cart) {
    this.setState({cart: cart})
  }
  handleError: function(err) {
    console.error(err)
  }
  render: function() {
    return (
      <div>
        <ProductPurchase onCartAdded={this.handleCartAdded} onError={this.handleError} />
        <CartSummary cart={this.state.cart} />
      </div>
    )
  }
})

This way, the components are decoupled from each other – and data/functions can only be passed in by a clear contract (props).

This simple style of event handling is a lot more explicit and easier to debug – so I would really only resort to a Flux style architecture if your app is getting really complex and/or you have a lot of components that all need to communicate with each other in a complex manner.