17

Almost all tutorials I found about flux emits only one event per store (emitChange). I don't really know, that it is intentional, or just the consequence of the tutorials simplicity.

I try to implement a store, that corresponds to the CRUD architecture, and I'm wondering if it would be a good design decision to emit different events for each CRUD method.

The relevant part of one of my stores look like this:

var UserStore = _.extend({}, EventEmitter.prototype, {

    emitChange: function() {
        this.emit('change');
    },

    emitUserAdded: function() {
        this.emit('userAdded');
    },

    emitUserUpdated: function() {
        this.emit('userUpdated');
    },

    emitUserDeleted: function() {
        this.emit('userDeleted');
    },

    // addListener, removeListener in the same manner
});

If my approach is wrong, how would I tell my components the type of event, that happend (for example: delete or update)

Jack7
  • 1,310
  • 12
  • 16
gsanta
  • 754
  • 6
  • 21
  • I'm not familiar with `react` specifically, but the main considerations in general are balancing writing a lot of boilerplate wire-up code to have discrete event types for every entity vs having every update event handler fire whenever an `update` is published instead of having one event handler fire when a `userUpdated` is published. How much horsepower does your runtime environment have? – Matt Mills Jun 27 '15 at 13:40
  • 'How much horsepower does your runtime environment have?' - What does this question mean? – gsanta Jun 27 '15 at 13:49
  • I think in react it's not appropriate having one update event, because every store represents a standalone entity. So the event must come from for example the UserStore, so I could not fire a general update event. However I could fire a simple change event from my UserStore and give as a parameter whether it is an update or smth else. I just don't know if that would be the best approach. – gsanta Jun 27 '15 at 13:51
  • "How much horsepower" is a car analogy...it means "how powerful or capable" is the runtime. If you're running in node on a server, you've got a lot more "horsepower" than if you're running on a lowest-common-denominator user's browser. – Matt Mills Jun 27 '15 at 13:56
  • Obviously I'm running it in a browser :) But I don't think that performance would be a bottleneck here, I just merely ask, that is it a good design decision or not (from a code quality perspective). – gsanta Jun 27 '15 at 14:06
  • "Obviously I'm running it in a browser :)". Unfortunately not, it could be server side rendering, e.g. universal/isomorphic. – MattDuFeu Jun 29 '15 at 14:54

2 Answers2

12

Flux as a design pattern is built on top of the idea that all data resides in "stores". Each store holds data for a given domain of information. As an example: in Flux, all comments would reside in a CommentStore.

When data is changed in a store, it should emit an event and all components that builds on top of this "information domain", should rerender and display the new domain data.

I've found that when a store is emitting multiple event types, it is more likely that components are not listening for that specific event, thus not rerendering itself when the domains data is altered.

This breaks the whole flux-pattern, and can easily create hard to find bugs where components are out of sync with the stores information.

I would instead recommend that you design your components from the "Law of Demeter" - each component should only know as much as it needs to.

So instead of the component listening to an event that says "commentList has been updated", you should create a commentListComponent that listens on a single store event. Thus, the component will listen on commentStore.on('change') - I usually let all stores emit an 'change' event. When the store emitts, you should rerender the data in the commenListComponent to reflect the store. If you use React, this is where you use setState.

var commentStore = _.extend({}, EventEmitter.prototype, {

updateComents: function() {
    // Update comments and emit
    this.emit('change');
},

removeComments: function() {
    // Remove comments and emit
    this.emit('change');
},

getState: function() {
    return {
        comments: this.comments,
        someOtherDomainData: this.meta,
    }
}
});

//commentListComponent.js
var commentListComponent = React.createClass({
    componentDidMount : function() {
        commentStore.on('change', this._commentChanged);
    },
    componentWillUnmount : function() {
        commentStore.off('change', this._commentChanged);
    },
    _commentChanged : function() { 
        this.setState({ comments : commentStore.getState().comments });
    },
    render : function() {
        var comments = // Build a list of comments.
        return <div>{comments}</div>
    }
})

This makes the data flow much more simple, and avoids hard to spot errors.

Silfverstrom
  • 28,292
  • 6
  • 45
  • 57
  • 1
    Thanks for the answer. However it's not clear for me, how you can differentiate the event types in this architecture. For example let's say, that the user deletes a comment. How can you tell the component, that the deletion was successful? – gsanta Jun 28 '15 at 07:27
  • 2
    You shouldn't. Thats the point. Let the store handle all the logic, and let the component just render the state that the store returns. a component that renders all comments in a list does not need to know if a comment is removed, it just need to rerender itself with all comments. – Silfverstrom Jun 28 '15 at 07:48
  • 9
    Ok, I see that the CommentList component does not have to know this. But imagine a popup component, that notifies the user that the deletion was successful (name it to CommentPopup). So this component needs some event, that tells it, that a deletion happened, how would you solve it? – gsanta Jun 28 '15 at 10:41
  • 1
    I solved a similar problem (showing the popup after the action completed) by having the action return a promise and binding the code triggering the popup to the "success" handler of the promise. For example, the "delete comment" button onclick handler would contain: `CommentActions.deleteComment(id).then(() => { this.setState({showDeletedPopup: true}) })`. I have no idea if this is idiomatic to the Flux workflow, but it worked. – Dan Keder Aug 16 '16 at 14:32
  • Imagine a map component that displays complex information. It would be too expensive to refresh all objects (markers positions, labels, colors, icons, etc) with this single change event. So what would be the possibilities to optimize all this and refresh only what is necessary? – TeChn4K Jan 11 '17 at 10:10
  • 2
    TeChn4K - This depends on your specific setup. As you say, refreshes are expensive, especially when refreshing the DOM. Flux was never built on the idea that everything should actually be rerendered - just that it should look like everything is rerendered from the programmers point of view. Thus, Flux thrives in environments where a virtual dom is utilized. This is because a virtual dom(react, vue etc) minimizes the number of rerenders necessary. In cases where a virtual dom can't optimize this enough out of the box, you can usually hint the framework when it is appropriate to refresh its data – Silfverstrom Jan 11 '17 at 10:34
0

My use-case force me to use distinct events. I have a map component that displays many complex objects and it would be very expensive to refresh each.

EventEmitter2 implements multi-leveling and wildcard events. It allows me to listen "change" events, or more specifically sub-level events "change.add" "change.remove" ...

this.emit("change.add");

can be listening by

myStore.on('change.*', callback);
myStore.on('change.add', callback);

In this way, many of my components can simply listen to the "change.*" event. And more complex components who needs optimization can listen specific events.

The best of both worlds

PS: don't forget to enable wildcards. See documentation

Community
  • 1
  • 1
TeChn4K
  • 2,317
  • 2
  • 21
  • 23