Taken from facebook presentation Flux: A Unidirectional Data Flow Architecture for React Apps.
Action -> Dispatcher -> Store -> View
createMessage: function (text) {
AppDispatcher.dispatch({
type: ActionTypes.NEW_MESSAGE,
text
})
}
- 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()
- 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;
}
//...
}
}
- 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')
})
// ...
});
- Tree of React Components
- Controller views are near the root, listen for change events
- On change, controller views query stores for new data
- Based on Functional-Reactive principles
- Unidirectional data flow
- Composability
- Predictable, Reliable, Testable
- Declarative: what the UI should look like
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;
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)
}
- 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
- Boost performance in React's shouldComponentUpdate()
- React.addons.PureRenderMixin
- immutable-js
- Lee Byron's talk on how immutable JS is efficient
- 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)
- 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!
- 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
- Talk Publication
- Original Flux Chat Example
- Facebook Flux Deficiency
- How to handle multiple stores of same type in Flux
- Simple Data Flow in React Apps using Flux and Backbone
- A (little more) complex react and flux example - github project demonstrating react concepts
- Testing Flux Applications
- A Flux & React demo