Emitting and handling global events with react
Asked Answered
E

3

21

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?

Ehf answered 14/12, 2014 at 15:30 Comment(0)
R
20

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.

Rameses answered 14/12, 2014 at 20:43 Comment(0)
P
8

Flux is used to decouple programming constructs (including AJAX calls).

Here is the diagram from the Flux Docs

Flux Architecture Diagram

Dispatcher in Flux architecture always remains in Global scope. So any operation involved with the dispatcher always occurs in the global scope. Also, dispatcher and event-system has a slight difference, event system always registers callbacks bound to a specific event but in case of dispatchers, all the callbacks are bound to all the events.

How to use Dispatcher? (Simplified approach without the use of Stores and ActionCreators)

  1. If other parts of application is affected by this AJAX call then you shouldn't make this AJAX call from this component, rather move the AJAX call into a new file and a function. Eg using CommonJS,

    // CartApiUtils.js
    module.exports = {
        addToCart: function(item){
            // AJAX call
        }
    }
    
  2. Create an AppDispatcher (which is common throughout the application) using Flux's Dispatcher class

    var appDispatcher = new Dispatcher();
    
  3. In addToCart() function, on successful AJAX response, dispatch an event using AppDispatcher:

    appDispatcher.dispatch({
        actionType: 'cart.newItemAdded',
        data: dataFromAjax
    });
    
  4. In your app, wherever you want to use this event, you can just register a function to dispacher.

    appDispatcher.register(function(payload){
        if(payload.actionType === 'cart.newItemAdded'){
            // payload.data contains the data
        }
    });
    

This is a simplified approach. In a more normalized structure and larger app, you should use Stores (which is something like the Model layer of MVC, but not identical) and ActionCreator where any interaction on the view layer is an action by the user and any response by the AJAX call also becomes an action from the Server.

Thumb rule is that, Views must be populated (or updated) from Stores and Stores must be updated on Dispatcher events.

Protrusive answered 14/12, 2014 at 20:40 Comment(3)
Note: don't use the dispatcher you linked in your components because it offers no way to unregister, which means no way to clean up in componentWillUnmount. If you wish to use it without stores, just use a plain event emitter (e.g. events.EventEmitter in node).Pension
How do you get a reference to the singleton Dispatcher in each module?Deify
You need to store the dispatched state in a context that wrap the two screensHayes
W
2

What about using npm's "react-global-events" ?

https://www.npmjs.com/package/react-global-events

Welcome answered 4/5, 2017 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.