Reputation: 7866
I'm trying to learn Flux, and having watched and read these amazing resources
I still don't understand which part of Flux
architecture (Action
, Dispatcher
or Store
) is responsible for talking to the API, provided that my API is asynchronous, and is able to push data - i.e. I get an event when new data becomes available.
This image suggests that an Action
is talking to API, however multiple code examples show that Action
is only triggering Dispatcher
..
Upvotes: 2
Views: 591
Reputation: 1056
Reto Schläpfer explains how he approaches this same problem with great clarity:
The smarter way is to call the Web Api directly from an Action Creator and then >make the Api dispatch an event with the request result as a payload. The Store(s) >can choose to listen on those request actions and change their state accordingly.
Before I show some updated code snippets, let me explain why this is superior:
There should be only one channel for all state changes: The Dispatcher. This >makes debugging easy because it just requires a single console.log in the >dispatcher to observe every single state change trigger.
Asynchronously executed callbacks should not leak into Stores. The consequences >of it are just to hard to fully foresee. This leads to elusive bugs. Stores >should only execute synchronous code. Otherwise they are too hard to understand.
Avoiding actions firing other actions makes your app simple. We use the newest >Dispatcher implementation from Facebook that does not allow a new dispatch while >dispatching. It forces you to do things right.
Full article: http://www.code-experience.com/the-code-experience/
Upvotes: 0
Reputation: 569
After a couple months working with React + Flux, I've faced the same question and have tried some different approaches. I've reached the conclusion that the best way is to have the actions deal with data updates, both remote and local:
# COMPONENT
TodoItems = React.createClass
componentDidMount: ->
TodoStore.addListener("CHANGE", @_onChange)
_onChange: ->
@setState {
todos: TodoStore.get()
_onKeyDown: (event) ->
if event.keyCode == ENTER_KEY_CODE
content = event.target.value.trim()
TodoActions.add(content)
render: ->
React.DOM.textarea {onKeyDown: @_onKeyDown}
# ACTIONS
class TodoActions
@add: (content) ->
Dispatcher.handleAction({type: "OPTIMISTIC_TODO_ADD", todo: {content: content}})
APICall.addTodo({content: content})
# STORE
class TodoStore extends EventEmitter
constructor: ->
@todos = [] # this is a nice way of retrieving from localStore
@dispatchToken = @registerToDispatcher()
get: ->
return @todos
registerToDispatcher: ->
Dispatcher.register (payload) =>
type = payload.type
todo = payload.todo
response = payload.response
switch type
when "OPTIMISTIC_TODO_ADD"
@todos.push(todo)
@emit("CHANGE")
when "TODO_ADD"
# act according to server response
@emit("CHANGE") # or whatever you like
#### APICall
class APICall # what can be called an 'action creator'
@addTodo: (todo) ->
response = http.post(todo) # I guess you get the idea
Dispatcher.handleAction({type: "TODO_ADD", response: response})
As you can see, the "juice" is within TodoActions
. When a todo gets added, TodoActions.add()
can trigger an optimistic UI update via OPTIMISTIC_TODO_ADD
that will insert into TodoStore.todos
. In parallel it knows that this must be communicated to the server.
An external entity - ApiCall
(that can be considered an action creator) - is responsible to deal with the remote part of this action and when you get a response it follows its normal course to TodoStore
that can act accordingly.
If you make the stores directly responsible for remote content management you will be adding an extra layer of complexity to it, which made me less confident about the data state at a certain point.
Lets imagine it:
class TodoActions
# TodoActions is `dumb`, only passes data and action types to Dispatcher
@add: (content) ->
Dispatcher.handleAction({type: "TODO_ADD", todo: {content: content}})
# APICall.addTodo({content: content})
class TodoStore extends EventEmitter
# ...
registerToDispatcher: ->
# ...
when "TODO_ADD"
@todos.push(todo)
# now the store has to push it to the server
# which means that it will have to call actions or the API directly = BAD
# lest assume:
APICall.addTodo({content: content})
# it also generates some uncertainty about the nature of the event emit:
# this change can guarantee that data was persisted within the server.
@emit("CHANGE")
The solution I've presented first offers a nice way of doing optimistic updates to the UI, handling errors and displaying loading indications as far as I've experienced.
Upvotes: 2
Reputation: 2006
If you look at the role of Actions as informing Stores of updated state data, it seems sensible that API calls that actually get the new data should come before the Action is called (e.g. in the event handlers of the component). However, you may not want API-related logic scattered throughout your Views. To avoid this, a module of ActionCreators is sometimes introduced between View and Action in the above diagram.
Methods for making API calls and handling the returned data by calling appropriate Actions can be collected in ActionCreators, so they will be loosely coupled to your Views. For example,
user clicks login ->
click handler calls ActionCreator.login(), which makes the API call ->
result is passed to Stores by calling Actions ->
Stores update their state accordingly
If your server can push updates through something like websockets, the corresponding event listeners can call methods defined in ActionCreators as well, so all your actions are emitted from one place. Or you could split up user-initiated ActionCreators and server-initiated ActionCreators into separate modules. Either way, I think this achieves a good separation of concerns.
Upvotes: 2