Unidirectional Data Flow FTW!
http://slides.com/tylergraf/unidirectional-ftw
Tyler Graf

- Web developer at FamilySearch
- Tree Team
- Web awesomeness advocate
- Photography Buff
- Ameteur Filmmaker
- Typography Nerd
- Utah's Adult Hide and Seek Championship
- 2nd runner up 2013-2016
@tylergraf
github.com/tylergraf
flickr.com/tylergraf
vimeo.com/tylergraf


#wife {
right: 100%;
margin: 0;
}

MVC
is awesome.
Action
Controller
Model
View
- Model and View have double binding
- Works great for small apps.
Enter Facebook

MVC
Action
Controller
Model
View
Model
View
Model
View
Model
View
Model
View
Model
View
- Doesn't scale well
- Can get unpredictable
isn't so awesome.

But...
is still awesome.
Controller
Model
View
Model
View
Model
View
Model
View
Model
View
Model
View
Controller
Controller
MVC
Action
Action
Action
...and poking is still weird.
Dispatcher
Store
View
FLUX
Action
- Unidirectional
- More predictable
Other stuff
FLUX
Action
{
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
FLUX
var dispatcher = new Dispatcher();
var action = {
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
dispatcher.dispatch(action);
Dispatcher
FLUX
var _people = [];
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
case 'DO_SOMETHING':
// do something else
break;
}
PersonStore.emitChange();
});
Store
FLUX
var _people = [];
var PersonStore = Object.assign(EventEmitter.prototype, {
getPersonById: function(id) {
return _people.find(id);
},
emitChange: function() {
this.emit('change');
},
addChangeListener: function(callback) {
this.on('change', callback);
},
removeChangeListener: function(callback) {
this.removeListener('change', callback);
}
});
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
case 'DO_SOMETHING':
// do something else
break;
}
PersonStore.emitChange();
});
Store
FLUX
var PersonView = React.createClass({
getInitialState: function() {
return PersonStore.getPersonById(1);
},
componentDidMount: function() {
PersonStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
PersonStore.removeChangeListener(this._onChange);
},
render: function() {
return (
<PersonView personObj={this.state.person} />
);
},
_onChange: function() {
this.setState(PersonStore.getPersonById(1));
}
});
View
Dispatcher
Store
View
FLUX
Action
Other stuff
Dispatcher
Store
View
FLUX
Action
Store
Store
View
View
View
View
View

Why FLUX?
- All changes to stores go through a single dispatcher
- Command Queue type design pattern
- Quite Predictable
- Unidirecional
- Facebook said it's faster to get up to speed
Dan Abramov

Dispatcher
Store
View
REDUX
Action
Other stuff
Reducer
REDUX PRINCIPLES
- Single Source of Truth (Singe Store)
- State is read-only
- Changes are made with pure functions
How is it different?
REDUX
Action
{
type: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
REDUX
var PersonStore = Redux.createStore(reducer);
var action = {
type: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
PersonStore.dispatch(action);
Dispatcher
var dispatcher = new Dispatcher();
var action = {
actionType: 'UPDATE_PERSON',
person: {
id: '1',
name: 'Fabio'
}
}
dispatcher.dispatch(action);
Flux
Redux
REDUX
Reducer
var _people = [];
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'UPDATE_PERSON':
_people = _people.map(p=>{
if(p.id === payload.person.id){
return payload.person;
}
return p;
});
break;
}
PersonStore.emitChange();
});
function PersonReducer(state, action) {
if(action === undefined) return state;
switch (action.type) {
case 'UPDATE_PERSON':
state._people = state._people.map(p=>{
if(p.id === action.person.id){
return action.person;
}
return p;
});
break;
}
return state;
};
Flux
Redux
PersonStore.addChangeListener(this._onChange);
PersonStore.removeChangeListener(this._onChange);
AppStore.subscribe();
AppStore.unsubscribe();
Flux
Redux
REDUX
View
Single Data Store
dispatcher.register(PreferencesStateHandler);
dispatcher.register(PedigreeStateHandler);
...
PreferencesStore.getPreference('pref');
//{
// pref object
//}
PedigreeStore.getPedigreeById(1);
//{
// pedigree object
//}
var stores = {
pedigree: PedigreeReducer,
prefs: PreferencesReducer
};
var AppStore = Redux.combineReducers(stores);
AppStore = Redux.createStore(AppStore);
...
AppStore.getState();
//{
// prefs: {
//
// },
// pedigree: {
//
// }
//}
Flux
Redux
Reducer Functions
function getHighestNumber(max, current){
return Math.max(max, current);
}
var numbersArr = [0,1,2,7,3,4,5,1,2,3,4,5,6,6];
numbersArr.reduce(getHighestNumber);
//7
function add(runningTotal, current){
return runningTotal + current;
}
var numbersArr = [0,1,2,7,3,4,5,1,2,3,4,5,6,6];
numbersArr.reduce(add);
//49
Reducer Functions
function reducer(state, action) {
switch (action.type) {
case 'DO_THINGS':
state.doing = 'things';
break;
case 'DO_STUFF':
state.doing = 'stuff';
break;
}
return state;
};
AppStore.dispatch({type: 'DO_STUFF'});
AppStore.dispatch({type: 'DO_THINGS'});
AppStore.getState();
//{
// doing: 'things'
//}
Pure Reducer Functions
dispatcher.register(function(payload) {
switch (payload.actionType) {
case 'FETCH_PERSON':
PERSON_API.getPerson(payload.person.id);
break;
}
PersonStore.emitChange();
});
function PersonReducer(state, action) {
if(state === undefined) return state;
switch (action.type) {
case 'FETCH_PERSON':
state.fetchingPerson = true;
break;
}
return state;
};
var PersonStore = Redux.createStore(PersonReducer);
Flux
Redux
Easily Testable
Time Travel

Polymer Element
<dom-module id="person-element">
<template>
<style>
:host {display: block;}
</style>
<p class="name">[[person.name]]</p>
</template>
<script>
Polymer({
is: 'person-element',
properties: {
personId: String,
person: {
type: Object
}
},
// Element Lifecycle
ready: function() {},
attached: function() {
AppStore.subscribe(this._onChange);
},
detached: function() {
AppStore.unsubscribe(this._onChange);
},
_onChange: function() {
var state = AppStore.getState();
this.set('person', state.person);
}
});
</script>
</dom-module>
<person-element person-id="1"></person-element>
polymer-redux
var store = Redux.createStore(function(state, action) {
return state;
});
var ReduxBehavior = PolymerRedux(store);
var MyElement = Polymer({
is: 'my-element',
behaviors: [ ReduxBehavior ],
created: function() {
var state = this.getState();
this.dispatch({type: "DO_STUFF"});
}
});
property bindings
Polymer({
is: 'person-element',
behaviors: [ ReduxBehavior ],
properties: {
person: {
type: Object,
statePath: 'person'
}
}
});
function PersonReducer(state, action) {
if(state === undefined) return state;
switch (action.type) {
case 'UPDATE_PERSON':
state.person = action.person;
break;
}
return state;
};
Bindings
- https://github.com/tur-nr/polymer-redux
- https://github.com/reactjs/react-redux
- https://github.com/angular-redux/ng-redux
- https://github.com/angular-redux/ng2-redux
Demo

Unidirectional Data Flow FTW!
By Tyler Graf
Unidirectional Data Flow FTW!
- 1,034