dark_ruby
dark_ruby

Reputation: 7866

In Flux what is responsible for direct talking to API

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.. Flux architecture

Upvotes: 2

Views: 591

Answers (3)

Ignacio
Ignacio

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

jvalente
jvalente

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

Adam Stone
Adam Stone

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

Related Questions