Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: AppStore listener must be a function

fellow devs!

I have been digging into Flux / React with thanks to the tutorials over at Egghead. Although I have heard that React is moving, I have met with some changes in the React library while following said tutorials.

Up until now I have been able to fix them all. Now, I have run into a brick wall concerning the Store. As the tutorial states, I am making a shopping cart, in which the user can add things to their cart. Not that complicated. The actual adding works, but the re-rendering is not triggered. The following code gives the added error (full code will be listed below as well):

addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
},

Which results in Uncaught TypeError: listener must be a function. The callback parameter is undefined (so that's where the problem lies). However, because I am more than new React's way of working, I'm having trouble in locating the problem. The following snippet is the Store from the tutorial:

var AppStore = assign(EventEmitter.prototype, {

    emitChange: function() {
        this.emit(CHANGE_EVENT);
    },
    addChangeListener: function(callback) {
        this.on(CHANGE_EVENT, callback);
    },
    removeChangeListener: function(callback) {
        this.removeChangeListener(CHANGE_EVENT, callback);
    },
    getCart: function() {
        return _cartItems;
    },
    getCatalog: function() {
        return _catalog;
    },
    getCartTotals: function() {
        return _cartTotals();
    },
    dispatcherIndex: AppDispatcher.register(function (payload) {
        var action = payload.action;

        switch(action.actionType) {
            case AppConstants.ADD_ITEM:
                _addItem(payload.action.item);
                break;
            case AppConstants.REMOVE_ITEM:
                _removeItem(payload.action.index);
                break;
            case AppConstants.INCREASE_ITEM:
                _increaseItem(payload.action.index);
                break;
            case AppConstants.DECREASE_ITEM:
                _decreaseItem(payload.action.index);
                break;
        }

        AppStore.emitChange();

        return true;
    })
});

Take note that this is code from a tutorial at Egghead.io and I am in no-way the owner (and if they so wish I will remove said code).

If more code or explanation is required, I'll be happy to oblige!

Thanks people :)

EDIT 1: The component that is supposed to listen to change, but does not:

var Cart = React.createClass({

    getInitialState: function() {
        return cartItems();
    },
    componentWillMount: function() {
        debugger;
        AppStore.addChangeListener(this.onChange);
    },
    componentDidMount: function() {
        debugger;
        AppStore.addChangeListener(this.handleChange);
    },
    handleChange: function() {
        debugger;
        this.forceUpdate();
    },
    _onChange: function() {
        debugger;
        this.setState(cartItems());
    },
    render: function() {
        var total = 0;
        var items = this.state.items.map(function (item, i) {
            var subtotal = item.cost * item.qty;
            total +=subtotal;
            return (
                <tr key={i}>
                    <td><RemoveFromCart index={i} /></td>
                    <td>{item.title}</td>
                    <td>{item.qty}</td>
                    <td>
                        <Increase index={i} />
                        <Decrease index={i} />
                    </td>
                    <td>${subtotal}</td>
                </tr>
            );
        });

        return (
            <table className="table table-hover">
                <thead>
                    <tr>
                        <th></th>
                        <th>Item</th>
                        <th>Qty</th>
                        <th></th>
                        <th>Subtotal</th>
                    </tr>
                </thead>
                <tbody>
                    {items}
                </tbody>
                <tfoot>
                    <tr>
                        <td colSpan="4" className="text-right">Total</td>
                        <td>${total}</td>
                    </tr>
                </tfoot>
            </table>
        );
    }
});
like image 634
Nickvda Avatar asked Nov 23 '15 14:11

Nickvda


1 Answers

To me, it sounds like you've worked out most of this pattern but you've got a little confused. There's one last step that you need to include, and that is actually subscribing your components to the events that the store is pushing out.

this.emit(CHANGE_EVENT);

This will simply push something out. If no-one is listening, then nothing will know to change, so in your components, you need to actually listen for this event.

For a component, that might look something like:

React.createClass({
    componentDidMount: function() {
        // Called after react has rendered the HTML in the DOM.
        AppStore.addChangeListener(this.handleChange);
    },

    render: function() {
        return <div>{AppStore.someData}</div>
    },

    handleChange: function() {
        this.forceUpdate();
    }
});

NOTE: this.emit should not be calling addChangeListener. It should be triggering the callbacks that you have subscribed using the addChangeListener method.

Potential Improvements

  • One thing to note is that, despite all your data being in the AppStore, not all of your components will care about all of the data. For example, if you've got a Name component, that renders the users name, they only care about NAME_CHANGE_EVENT. They don't want to re-render every single time a little thing happens. This is needless processing by React, although React will surmise nothing has changed and won't update the DOM.
like image 150
christopher Avatar answered Nov 01 '22 09:11

christopher