React, Flux, State and Stores
Asked Answered
D

2

19

I find the example todo flux app to be a bit lacking so I'm trying to get my head round things by developing an application to learn and experiment.

The application is a drag and drop fruit basket organiser. I have several baskets which can have various pieces of fruit dragged between them. You can highlight a piece of fruit by clicking on it and the last dragged item will remain highlighted.

Based on this I have 3 stores:

  • FruitStore
  • BasketStore
  • AppStateStore - To track last clicked and last dragged fruit

When a user action occurs a FruitAction is dispatched and handled by either the AppStateStore if the fruit has been clicked or all stores if the fruit has been moved to another basket.

The main AppView component listens to change events from both the FruitStore and the AppStateStore and re-renders.

My questions are:

  • Is this a good approach for this scenario?
  • Should the AppView be listening to multiple stores? How should I prevent the AppView from rendering several times in a row? Right now, when a fruit has been moved, both FruitStore and AppStateStore fire change events causing two renders in a row.
  • The Flux article on the React site shows the view dispatching an action object (e.g. AppDispatcher.dispatch(TodoActions.updateText()) ) but would it be better if the action dispatched itself (e.g. just FruitActions.moveBasket() ) and the AppView is left unaware of the AppDispatcher?
  • Currently only the AppView listens to the stores but should the individual Fruit components listen to the AppStateStore to re-render only themselves if they are to be highlighted?
  • Is there a more complete example of the Flux architecture or something similar?
Doubleripper answered 29/5, 2014 at 10:39 Comment(5)
Have you published your example code anywhere?Boehike
I voted too broad because that really is too many questions for a single question. I think it needs to be a bit more focused.Blackberry
@demongolem: Thanks for the tips. I'll keep that in mind in the future.Doubleripper
@Lucas: Not at this timeDoubleripper
You missed one term from your title - Redux which is the most famous flux implementation for react.Tacklind
P
13
  • The approach sounds pretty solid. I would create completely different actions for every interaction, e.g. FruitClicked and FruitDragged could both be actions, and the stores would watch for the ones they care about.
  • AppView should listen to all the stores that it gets data from. If you call setState more than once in the same tick, React will intelligently merge them, so you're not actually rendering more than once.
  • The article on the React site does talk about actions that dispatch themselves under Creating Semantic Actions. In the code block a few scroll-lenghts up the page, you can see the code:

    _onDestroyClick: function() { 
      TodoActions.destroy(this.props.todo.id); 
    } 
    
  • The article also talks about which components should listen to stores in Listening to Changes with a Controller-View:

    We need a React component near the top of our component hierarchy to listen for changes in the store. In a larger app, we would have more of these listening components, perhaps one for every section of the page. In Facebook's Ads Creation Tool, we have many of these controller-like views, each governing a specific section of the UI. In the Lookback Video Editor, we only had two: one for the animated preview and one for the image selection interface.

    and

    Occasionally we may need to add additional controller-views deeper in the hierarchy to keep components simple. This might help us to better encapsulate a section of the hierarchy related to a specific data domain. Be aware, however, that controller-views deeper in the hierarchy can violate the singular flow of data by introducing a new, potentially conflicting entry point for the data flow. In making the decision of whether to add a deep controller-view, balance the gain of simpler components against the complexity of multiple data updates flowing into the hierarchy at different points. These multiple data updates can lead to odd effects, with React's render method getting invoked repeatedly by updates from different controller-views, potentially increasing the difficulty of debugging.

  • If you're interested in the flux architecture, you may be interested in Fluxxor (disclaimer: I wrote it), which (hopefully) helps build out a flux-based application. It has a dispatcher that's a little more robust and a few other changes (e.g. non-global stores/actions, React mixins) to make things a bit easier in many cases.
Pyrometer answered 29/5, 2014 at 16:14 Comment(4)
Thanks for the clarifications. I looked at Fluxxor before I started this and it looks great but I wanted to understand the nitty gritty before leaving it to a library. Thanks for correcting my on the 'Semantic Actions' as well. I obviously misread that. You talk about React merging actions into one tick; does that mean React uses rAF or something similar when there is a change?Doubleripper
@Doubleripper I'm not 100% sure on this, but I think React just renders on the next tick (via e.g. setTimeout) by default; there's a RAF strategy in the React repo/codebase, but I don't think it's used at the moment.Pyrometer
An update: React automatically merges renders after a synthetic event (e.g. onClick) has been processed; if you change state asynchronously, e.g. in a setTimeout or Ajax handler, it will only batch updates if you wrap the state changes in React.addons.batchedUpdates (added in v0.12.0).Pyrometer
What about that multiple renders question? Is that an issue? My understanding is that, due to DOM diffing, a DOM component will render if there is an actual change in the React component. Thus, emitting changes will not automatically re-render anything.Ossify
S
5

It's hard to say whether the approach is a good one without understanding the application a bit more.

However, it seems to me that clicking the fruit should be handled by the FruitStore. That is, a fruit has gained an active state, has become draggable. If this affects the application as a whole somehow, then perhaps this would cause a change in the AppStateStore as well. Likewise, moving a fruit from one basket to another seems like the domain of the BasketStore.

I imagine the FruitStore containing a private data structure like this:

var _fruit = {
  1234: {
    id: '1234',
    type: 'apple',
    active: false
  },
  2345: {
    id: '2345',
    type: 'orange',
    active: false
  },
  3456: {
    id: '3456',
    type: 'apple',
    active: false
  }
};

and I imagine the BasketStore having a private data structure that looks like this:

var _baskets = {
  4321: {
    id: '4321',
    name: 'Josephine\'s Basket',
    fruitIDs: [
      1234,
      2345
    ]
  },
  5432: {
    id: '5432',
    name: 'Harold\'s Basket',
    fruitIDs: [
      3456
    ]
  }
};

Thus the fruit "active" state is managed by the FruitStore, and the contents of the baskets are managed by the BasketStore.

So the AppView can listen to both stores, if this works well for your application. And as mentioned above, there is very little cost to calling the render() method repeatedly. This does not touch the DOM on every call -- this is one of React's greatest strengths. React will instead intelligently batch the calls and only update the "dirty" parts of the DOM, if any exist.

But perhaps you might want to have the baskets become controller-views. If you do choose to make your fruit the controller-views, be sure to cleanup the listeners in componentWillUnmount(), as the movement of fruit from basket to basket may require them to be destroyed and recreated. I think listening at the basket level makes more sense, but again, I don't understand your app that well.

To answer your last question:

Is there a more complete example of the Flux architecture or something similar?

Engineers at Facebook are working on a more complex application example now that will showcase the use of waitFor() and server-side persistent storage. We are hoping to release that soon.

Secede answered 5/7, 2014 at 18:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.