Skip to content

Instantly share code, notes, and snippets.

@avshabanov
Created October 13, 2015 02:26
Show Gist options
  • Save avshabanov/2239eeb582de5c6373dd to your computer and use it in GitHub Desktop.
Save avshabanov/2239eeb582de5c6373dd to your computer and use it in GitHub Desktop.
Flux Presentation Notes

Taken from facebook presentation Flux: A Unidirectional Data Flow Architecture for React Apps.

Flux Pattern

Action -> Dispatcher -> Store -> View

Flux message send

createMessage: function (text) {
  AppDispatcher.dispatch({
    type: ActionTypes.NEW_MESSAGE,
    text
  })
}

Dispatcher Usage

  • Three main methods: dispatch, register, waitFor.
  • waitFor is a killer feature - it waits for an update/change in certain data
var Dispatcher = require('Flux.Dispatcher')
module.exports = new Dispatcher()

Store

  • Each Store is a singleton
  • The locus of control within the application
  • Manages app state for a logical domain
  • Private variables hold the application data
  • Numerous collections or values can be held in a store

What they do:

  • Register a callback with the dispatcher
  • Callback is the only way data gets into the store
  • No setters, only getters: a universe unto itself
  • Emits an event when state has changed

Example:

dispatchToken

class MessageStore {

register(action => {
   var message = action.message;
   switch(action.type){
   //... code
   case ActionTypes.MESSAGE_DELETE:
     delete _messages[action.messageID]
     this.emit('change')
     break;
   }
   //...
  }

}

How to test Stores with Jest

  • Dispatcher methods get mocked ** e.g. callback = AppDispatcher.register.mock.calls[0][0].
jest.dontMock('MessageStore')
var AppConstants = require('AppConstants')
var ActionTypes = AppConstants.ActionTypes

describe('MessageStore', function () {
    var callback

    beforeEach(function () {
        AppDispatcher = require('AppDispatcher') // mock
        MessageStore = require('MessageStore') // real
        callback = AppDispatcher.register.mock.calls[0][0]
    })

    it('can create messages', function () {
        callback({
            type: ActionTypes.MESSAGE_CREATE,
            text: 'text'
        })
        var messages = MessageStore.getMessages()
        var firstKey = Objects.keys(messages)[0]
        expect(MessageStore.getMessages()[firstKey].text).toBe('text')
    })

    // ...
});

View & Controller Views

  • Tree of React Components
  • Controller views are near the root, listen for change events
  • On change, controller views query stores for new data

React's Paradigm

  • Based on Functional-Reactive principles
  • Unidirectional data flow
  • Composability
  • Predictable, Reliable, Testable
  • Declarative: what the UI should look like

React Data Lifecycle

Lifecycle:                 | Update:
Mounting and Unmounting    | New Props or State
                           |
getDefaultProps()          | componentWillReceiveProps() *
getInitialState()          | shouldComponentUpdate()
componentWillMount()       | componentWillUpdate()
render()                   | render()
                           |
-------- DOM Mutations Complete --------------------------
                           |
componentDidMount()        | componentDidUpdate()
                           |
-------------------------- |
                           |
componentWillUnmount()     |

Controller Methods:

  • getInitialState
  • render
  • componentDidMount
  • componentWillUnmount

Controller View Example:

var MessagesControllerView = React.createClass({
    getInitialState: function () {
        return { messages: MessageStore.getMessages() }
    },

    componentDidMount: function () {
        MessageStore.on('change', this._onChange)
    },

    componentWillUnmount: function () {
        MessageStore.removeListener('change', this._onChange)
    },

    render: function () {
        // SEE BELOW
    },

    _onChange: function () {
        this.setState({ messages: MessageStore.getMessages() })
    }
})

Example of render method in ControllerView:

render: function() {
    var messageListItems = this.state.messages.map(message => {
        return <MessageListItem key={message.id} messageID={messageID} text={message.text}/>
    })

    return <ul>{messageListItems}</ul>
}

Example of 'non-controller' view (MessageListItem):

var MessageActionCreators = require('MessageActionCreators')

var MessageListItem = React.createClass({
    propTypes = { // IMPORTANT: type checking
        messageID: React.PropTypes.number.isRequired,
        text: React.PropTypes.string.isRequired
    },

    render: function () {
        return <li onClick={this._onClick}>{this.props.text}</li>
    },

    _onClick: function () {
        MessageActionCreators.deleteMessage(this.props.messageID)
    }
})

Then: call deleteMessage

deleteMessage: messageID => {
    AppDispatcher.dispatch({
        type: ActionTypes.MESSAGE_DELETE,
        messageID
    })
}

in MessageDispatcher:

case ActionTypes.MESSAGE_DELETE:
    delete _messages[action.messageID];
    this.emit('change');
    break;

Initialization of the App

Usually done in a bootstrap module:

// AppBootstrap.js

var AppConstants = require('AppConstants')
var AppDispatcher = require('AppDispatcher')
var AppRoot = require('AppRoot.react')
var React = require('React')

require('FriendStore')
require('LoggingStore')
require('MessageStore')

module.exports = (initialData, elem) => {
    AppDispatcher.dispatch({
        type: AppConstants.ActionTypes.INITIALIZE,
        initialData
    })
    React.render(<AppRoot/>, elem)
}

Calling a Web API

  • Use a WebAPIUtils module to encapsulate XHR work
  • Start requests directly in the Action Creators or in the stores
  • Important: create a new action on success/error
  • Data MUST enter the system through an action

Immutable Data

  • Boost performance in React's shouldComponentUpdate()
  • React.addons.PureRenderMixin
  • immutable-js
  • Lee Byron's talk on how immutable JS is efficient

More Flux Patterns

  • LoggingStore - encapsulates analytics, metrics, etc.
  • Error handling with client ID / dirty bit ** For optimistics updates, for action create a clientID, if server comes back with an error then go back to the store and clean up using that clientID
  • Error handling with actions queue ** Another framework: relay (graphQL language), built on top of flux, coming to the public in 2015, inside of graphQL they have an action queue and down in the getters they pull from both the cached values and from actions view
  • Resolving dependencies in the controller-view ** Circular Dependencies can become a big problem. Resolve by stores not requiring each other (e.g. introduce a third stores)

Anti-Patterns for React+Flux

  • Getters in render()
  • Public setters in stores & the setter mindset trap
  • Application state/logic in React components ** React components should have only their own state, when they have shared data, this data should be moved into the separate store
  • Props in getInitialState ** Trying to create initial state from the props ** Violates source of truth of data ** state and props should be separate!

Flux relations

  • Not an MVC
  • Functional and FRP
  • Influenced by reactive and functional programming
  • Influenced by data flow programming
  • Distinct inputs and outputs
  • Most close to CQRS (command+data layer+query+view) ** Command is reified to the large object ** React is like a lightweight form of CQRS

Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment