Slide 1

Slide 1 text

React and Flux Building Applications with a Unidirectional Data Flow

Slide 2

Slide 2 text

React and Flux Building Applications with a Unidirectional Data Flow Bill Fisher @fisherwebdev

Slide 3

Slide 3 text

React and Flux Bill Fisher @fisherwebdev https://speakerdeck.com/fisherwebdev/flux-meetup http://facebook.github.io/flux/ http://facebook.github.io/react/

Slide 4

Slide 4 text

Bill Fisher @fisherwebdev #reactjs #fluxjs http://facebook.github.io/flux/ http://facebook.github.io/react/ https://speakerdeck.com/fisherwebdev/flux-meetup

Slide 5

Slide 5 text

https://speakerdeck.com/fisherwebdev/flux-meetup "Couch" by Brian Teutsch, used under CC BY 2.0 / Modified from original

Slide 6

Slide 6 text

"Couch" by Brian Teutsch, used under CC BY 2.0 / Modified from original https://speakerdeck.com/fisherwebdev/flux-meetup

Slide 7

Slide 7 text

Model Model Model Model

Slide 8

Slide 8 text

Model Model Model Model

Slide 9

Slide 9 text

Model Model Model Model

Slide 10

Slide 10 text

Model Model Model Model

Slide 11

Slide 11 text

Model Model Model Model

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

"Tangled wires, Freegeek, Portland, Oregon, USA" by Cory Doctorow, used under CC BY 2.0\

Slide 16

Slide 16 text

"Spaghetti? Yum!" by Dan McKay, used under CC BY 2.0

Slide 17

Slide 17 text

Dispatcher Action Store View

Slide 18

Slide 18 text

"Couch" by Brian Teutsch, used under CC BY 2.0 / Modified from original

Slide 19

Slide 19 text

THE PLAN Anatomy of a React + Flux Application Actions & Action Creators The Dispatcher Stores + Testing Views, Controller-views and React Initialization, Interacting with a Web API, Immutable Data

Slide 20

Slide 20 text

FILES Primary: action creators, dispatcher, stores, React components Secondary: bootstrap, utilities, constants

Slide 21

Slide 21 text

.   ├──  css   │      └──  app.css   ├──  index.html   ├──  js   │      ├──  actions   │      │      └──  FooActionCreators.js   │      ├──  AppBootstrap.js   │      ├──  AppConstants.js   │      ├──  AppDispatcher.js   │      ├──  stores   │      │      ├──  FooStore.js   │      │      └──  __tests__   │      │              └──  FooStore-­‐test.js   │      ├──  utils   │      │      ├──  AppWebAPIUtils.js   │      │      ├──  FooUtils.js   │      │      └──  __tests__   │      │              ├──  AppWebAPIUtils-­‐test.js   │      │              └──  FooUtils-­‐test.js   │      └──  views   │              ├──  FooControllerView.react.js   │              └──  FooChild.react.js   └──  package.json

Slide 22

Slide 22 text

js   ├──  actions   │      └──  FooActionCreators.js   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──  AppDispatcher.js   ├──  stores   │      ├──  FooStore.js   │      └──  __tests__   │              └──  FooStore-­‐test.js   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──  views          ├──  FooControllerView.react.js          └──  FooChild.react.js .   ├──  css   │      └──  app.css   ├──  index.html   ├──   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   │   └──  package.json

Slide 23

Slide 23 text

js   ├──  actions   │      └──  FooActionCreators.js   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──  AppDispatcher.js   ├──  stores   │      ├──  FooStore.js   │      └──  __tests__   │              └──  FooStore-­‐test.js   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──  views          ├──  FooControllerView.react.js          └──  FooChild.react.js

Slide 24

Slide 24 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js

Slide 25

Slide 25 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 26

Slide 26 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 27

Slide 27 text

actions   └──  FooActionCreators.js Action ACTIONS & ACTION CREATORS

Slide 28

Slide 28 text

ACTIONS & ACTION CREATORS Actions: an object with a type property and new data Action creators: semantic methods that create actions collected together in a module to become an API

Slide 29

Slide 29 text

//  FooActionCreators.js   ! var  AppDispatcher  =  require('../AppDispatcher');   var  AppConstants  =  require('../AppConstants');   ! var  ActionTypes  =  Constants.ActionTypes;   ! module.exports  =  {   !    createMessage:  function(text)  {          AppDispatcher.dispatch({              type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }   ! };

Slide 30

Slide 30 text

!    createMessage:  function(text)  {          AppDispatcher.dispatch({              type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }

Slide 31

Slide 31 text

!    createMessage:  function(text)  {          AppDispatcher.dispatch({              type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }

Slide 32

Slide 32 text

!    createMessage:  function(text)  {          AppDispatcher.dispatch({              type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }

Slide 33

Slide 33 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 34

Slide 34 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 35

Slide 35 text

THE DISPATCHER AppDispatcher.js Dispatcher

Slide 36

Slide 36 text

THE DISPATCHER Essentially a registry of callbacks To dispatch, it invokes all the callbacks with a payload Flux dispatcher is a singleton; payload is an action Primary API: dispatch(), register(), waitFor() Base class is available through npm or Bower.

Slide 37

Slide 37 text

//  AppDispatcher.js   ! var  Dispatcher  =  require('Flux.Dispatcher');   ! //  export  singleton   module.exports  =  new  Dispatcher();  

Slide 38

Slide 38 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 39

Slide 39 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 40

Slide 40 text

Store stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js STORES

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

STORES 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

Slide 43

Slide 43 text

//  FooStore.js   ! var  _dispatchToken;   var  _messages  =  {};   ! class  FooStore  extends  EventEmitter  {   !    constructor()  {          super();          _dispatchToken  =  AppDispatcher.register((action)  =>  {   !            switch(action.type)  {   !                case  ActionTypes.MESSAGE_CREATE:                      var  message  =  {                          id:  Date.now(),                          text:  action.text                      }                      _messages[message.id]  =  message;                      this.emit('change');                      break;   !                case  ActionTypes.MESSAGE_DELETE:                      delete  _messages[action.messageID];                      this.emit('change');                      break;   !                default:                      //  no  op              }   !        });      }   !    getDispatchToken()  {          return  _dispatchToken;      }   !    getMessages()  {          return  _messages;      }   ! }   ! module.exports  =  new  FooStore();          _dispatchToken  =  AppDispatcher.register((action)  =>  {   !            switch(action.type)  {   !                case  ActionTypes.MESSAGE_CREATE:                      var  message  =  {                          id:  Date.now(),                          text:  action.text                      }                      _messages[message.id]  =  message;                      this.emit('change');                      break;   !                case  ActionTypes.MESSAGE_DELETE:                      delete  _messages[action.messageID];                      this.emit('change');                      break;   !                default:                      //  no  op              }   !        });

Slide 44

Slide 44 text

       _dispatchToken  =  AppDispatcher.register((action)  =>  {   !            switch(action.type)  {   !                case  ActionTypes.MESSAGE_CREATE:                      var  message  =  {                          id:  Date.now(),                          text:  action.text                      }                      _messages[message.id]  =  message;                      this.emit('change');                      break;   !                case  ActionTypes.MESSAGE_DELETE:                      delete  _messages[action.messageID];                      this.emit('change');                      break;   !                default:                      //  no  op              }   !        });

Slide 45

Slide 45 text

TESTING STORES WITH JEST Stores have no setters — how to test is not obvious Jest is Facebook’s auto-mocking test framework Built on top of Jasmine

Slide 46

Slide 46 text

callback  =  AppDispatcher.register.mock.calls[0][0];  

Slide 47

Slide 47 text

jest.dontMock('FooStore');   ! var  AppConstants  =  require('AppConstants');   ! var  ActionTypes  =  AppConstants.ActionTypes;   ! describe('FooStore',  function()  {   !    var  callback;   !    beforeEach(function()  {          AppDispatcher  =  require('AppDispatcher');          FooStore  =  require('FooStore');          callback  =  AppDispatcher.register.mock.calls[0][0];      });   !    it('can  create  messages',  function()  {          callback({              type:  ActionTypes.MESSAGE_CREATE,              text:  'test'          });          var  messages  =  FooStore.getMessages();          var  firstKey  =  Objects.keys(messages)[0];          expect(FooStore.getMessages()[firstKey].text).toBe(‘test’);      });   ! }); callback  =  AppDispatcher.register.mock.calls[0][0];  

Slide 48

Slide 48 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 49

Slide 49 text

js   ├──   │   ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  FooActionCreators.js AppDispatcher.js stores   ├──  FooStore.js   └──  __tests__          └──  FooStore-­‐test.js views   ├──  FooControllerView.react.js   └──  FooChild.react.js Dispatcher Action Store View

Slide 50

Slide 50 text

View views   ├──  FooControllerView.react.js   └──  FooChild.react.js VIEWS & CONTROLLER VIEWS

Slide 51

Slide 51 text

VIEWS & 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 With new data, they re-render themselves & children

Slide 52

Slide 52 text

getDefaultProps()   getInitialState()   componentWillMount()   render() componentWillReceiveProps()*   shouldComponentUpdate()   componentWillUpdate()   render() DOM  Mutations  Complete componentDidMount() componentDidUpdate() componentWillUnmount() Lifecycle:     Mounting  and  Unmounting Update:     New  Props  or  State *Called  only  with  new  props

Slide 53

Slide 53 text

getDefaultProps()   getInitialState()   componentWillMount()   render() componentWillReceiveProps()*   shouldComponentUpdate()   componentWillUpdate()   render() DOM  Mutations  Complete componentDidMount() componentDidUpdate() componentWillUnmount() Lifecycle:     Mounting  and  Unmounting Update:     New  Props  or  State *Called  only  with  new  props

Slide 54

Slide 54 text

! ! var  React  =  require('react');   ! ! ! ! ! ! ! ! ! module.exports  =  FooControllerView;   //  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !    render:  function()  {          //  TODO      }   ! });

Slide 55

Slide 55 text

//  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !    render:  function()  {          //  TODO      }   ! });

Slide 56

Slide 56 text

//  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !    componentDidMount:  function()  {          FooStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          FooStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   ! });

Slide 57

Slide 57 text

//  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !    getInitialState:  function()  {          return  {  messages:  FooStore.getMessages()  };      },   !    componentDidMount:  function()  {          FooStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          FooStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   !    _onChange:  function()  {          this.setState({  messages:  FooStore.getMessages()  });      },   ! });

Slide 58

Slide 58 text

var  FooControllerView  =  React.createClass({   !    getInitialState:  function()  {          return  {  messages:  FooStore.getMessages()  };      },   !    componentDidMount:  function()  {          FooStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          FooStore.removeListener('change',  this._onChange);      },   !                   !    _onChange:  function()  {          this.setState({  messages:  FooStore.getMessages()  });      },   ! }); //  FooControllerView.react.js      render:  function()  {          //  TODO      }

Slide 59

Slide 59 text

render:  function()  {      var  messageListItems  =  this.state.messages.map(message  =>  {          return  (                        );      });      return  (          
                 {messageListItems}          
     );   },

Slide 60

Slide 60 text

//  FooChild.react.js   ! var  React  =  require('react');   ! var  FooChild  =  React.createClass({   !    propTypes  =  {          id:  React.PropTypes.number.isRequired,          text:  React.PropTypes.string.isRequired      }   !    render:  function()  {          return  (              
  • {this.props.text}
  •          );      }   ! });   ! module.exports  =  FooChild;  

    Slide 61

    Slide 61 text

    INITIALIZATION OF THE APP Usually done in a bootstrap module Initializes stores with an action Renders the topmost React component

    Slide 62

    Slide 62 text

    module.exports  =  function(/*object*/  data,  /*DOMElement*/  elem)  {   !    var  dispatchToken  =  AppDispatcher.register(action  =>  {          if  (action.type  !==  Constants.ActionTypes.INITIAL_LOAD)  {              return;          }   !        //  wait  for  all  the  stores  to  initialize  before  rendering          var  tokens  =  [              ContentStore,              FriendStore,              LoggingStore,              ThemeStore,          ].map(store  =>  store.getDispatchToken());          AppDispatcher.waitFor(tokens);   !        React.render(,  elem);   !        AppDispatcher.unregister(dispatchToken);      });            Actions.initialize(data);  //  Creates  INITIAL_LOAD  action   };

    Slide 63

    Slide 63 text

    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.

    Slide 64

    Slide 64 text

    IMMUTABLE DATA Boost performance in React’s shouldComponentUpdate() React.addons.PureRenderMixin immutable-js: http://facebook.github.io/immutable-js/

    Slide 65

    Slide 65 text

    FLUX Dataflow Programming CQRS Functional and FRP MVC Reactive WHERE DOES FLUX FIT IN

    Slide 66

    Slide 66 text

    FLUX TOTALLY WORKS Dispatcher Action Store View

    Slide 67

    Slide 67 text

    THANK YOU!