Is it a good design, when a React Flux store emits multiple kinds of events?
Asked Answered
C

2

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)

Classieclassification answered 27/6, 2015 at 13:15 Comment(6)
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?Grijalva
'How much horsepower does your runtime environment have?' - What does this question mean?Classieclassification
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.Classieclassification
"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.Grijalva
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).Classieclassification
"Obviously I'm running it in a browser :)". Unfortunately not, it could be server side rendering, e.g. universal/isomorphic.Shatter
D
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.

Decolorize answered 27/6, 2015 at 17:1 Comment(6)
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?Classieclassification
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.Decolorize
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?Classieclassification
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.Casia
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?Rompish
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 dataDecolorize
R
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

Rompish answered 11/1, 2017 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.