Upgrade to Pro — share decks privately, control downloads, hide ads and more …

flux-react

Bill Fisher
October 20, 2014

 flux-react

Overview of how and why to build a React + Flux application.

Bill Fisher

October 20, 2014
Tweet

More Decks by Bill Fisher

Other Decks in Programming

Transcript

  1. React and Flux
    Building Applications with a Unidirectional Data Flow

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. https://speakerdeck.com/fisherwebdev/flux-react

    View Slide

  6. https://speakerdeck.com/fisherwebdev/flux-react

    View Slide

  7. Model
    Model
    Model
    Model

    View Slide

  8. Model
    Model
    Model
    Model

    View Slide

  9. Model
    Model
    Model
    Model

    View Slide

  10. Model
    Model
    Model
    Model

    View Slide

  11. Model
    Model
    Model
    Model

    View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. Dispatcher
    Action
    Store
    View

    View Slide

  18. 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

    View Slide

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

    View Slide

  20. .  
    ├──  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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

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

    View Slide

  27. 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

    View Slide

  28. //  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  
           });  
       }  
    !
    };

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. 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

    View Slide

  33. 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

    View Slide

  34. THE DISPATCHER
    AppDispatcher.js
    Dispatcher

    View Slide

  35. 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.

    View Slide

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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

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

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. //  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  
               }  
    !
           });

    View Slide

  43.        _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  
               }  
    !
           });

    View Slide

  44. 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

    View Slide

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

    View Slide

  46. 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];  

    View Slide

  47. 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

    View Slide

  48. 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

    View Slide

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

    View Slide

  50. 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

    View Slide

  51. REACT

    View Slide

  52. REACT AND THE DOM
    Component-based system for managing DOM updates
    Uses a “Virtual DOM”: data structure and algorithm
    Updates the DOM as efficiently as possible
    Huge performance boost
    Bonus: we can stop thinking about managing the DOM

    View Slide

  53. REACT’S PARADIGM
    Based on Functional-Reactive principles
    Unidirectional data flow
    Composability
    Predictable, Reliable, Testable
    Declarative: what the UI should look like, given props

    View Slide

  54. USING REACT
    Data is provided through props
    Rendering is a function of this.props and this.state
    “Re-render” (or not) on every state change
    Keep components as stateless as possible
    Component lifecycle and update cycle methods

    View Slide

  55. 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

    View Slide

  56. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  60. //  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()  });  
       },  
    !
    });

    View Slide

  61. 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  
       }

    View Slide

  62.    render:  function()  {  
           //  TODO  
       }

    View Slide

  63. render:  function()  {  
       return  (  
             
             
       );  
    },

    View Slide

  64. render:  function()  {  
       var  messageListItems  =  [];  
       return  (  
             
               {messageListItems}  
             
       );  
    },

    View Slide

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

    View Slide

  66. //  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;  

    View Slide

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

    View Slide

  68. 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.

    View Slide

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

    View Slide

  70. FLUX
    TOTALLY
    WORKS
    Dispatcher
    Action
    Store
    View

    View Slide

  71. THANK YOU!

    View Slide