$30 off During Our Annual Pro Sale. View Details »

Flux at 1.21 Gigawatts

Jake Trent
January 27, 2015

Flux at 1.21 Gigawatts

Here I present how I organize the elements of the Flux architecture in my mind, show how some of my Flux code has turned out, and make some observations of what I see as the good and bad of Flux. Where we're going, we don't need roads.

Jake Trent

January 27, 2015
Tweet

More Decks by Jake Trent

Other Decks in Technology

Transcript

  1. One Man’s Adventure at 1.21 GiGAwATtS FlUX @jaketrent

  2. What is FlUX A pattern for organizing state in your

    React.js apps ?
  3. Where does the 1.21 gIGaWAtTS come from? npm install flux

  4. Where does the 1.21 gIGaWAtTS Included: Dispatcher Invariant come from?

    Your Code: Stores Actions ApiHelper Components Router
  5. PlUToNIuM All you need is a little The parts of

    Flux…
  6. Web APIs API Helpers Actions Dispatcher Stores Components ThE CoDE

    CoNCePTs DaTA FlOW and
  7. Web APIs API Helpers Actions Dispatcher Stores CoMPoNEnTS User interface

    IN (from Stores) Change events Model data OUT (to Actions) Calls w/ UI data Components
  8. Web APIs API Helpers Dispatcher Stores AcTIoNS What the app

    can *do* IN (from Components) Calls w/ UI data OUT (to API Helpers) Data to persist Components Actions
  9. Web APIs API Helpers Actions Dispatcher Stores ApI HeLPeRS API

    data access layer IN (from Actions) Data to persist OUT (to Web API) Network requests Components
  10. Web APIs API Helpers Actions Dispatcher Stores ApI HeLPeRS API

    data access layer IN (from Web APIs) Network responses OUT (to Actions) Data from APIs Components
  11. Web APIs API Helpers Dispatcher Stores AcTIoNS What the app

    can *do* IN (from API Helpers) Data from APIs OUT (to Dispatcher) Wrapped action payloads Components Actions
  12. Web APIs API Helpers Dispatcher Stores AcTIoNS What the app

    can *do* Components Actions • Wrapped action payload makes all dispatched methods consistent
  13. Web APIs API Helpers Actions Stores DiSPaTChER Global message broker

    IN (from Actions) Wrapped action payloads OUT (to Stores) Wrapped action payloads Components Dispatcher
  14. Web APIs API Helpers Actions Stores DiSPaTChER Global message broker

    Components Dispatcher • Dispatches all payloads to all stores
  15. Web APIs API Helpers Actions Dispatcher StOReS Holds domain model

    data on the client IN (from Dispatcher) Wrapped action payloads OUT (to Components) Change events Model data Components Stores
  16. Web APIs API Helpers Actions Dispatcher StOReS Holds domain model

    data on the client Components • Listens for only dispatched payloads it cares about • Can defer payload handling by waiting for other stores • Cohesive around a domain • Can have multiple Stores
  17. Web APIs API Helpers Actions Dispatcher StOReS “When should I

    make another store?” Components Stores • Circular dependencies • Multiple domains
  18. Web APIs API Helpers Actions Dispatcher StOReS “When should I

    make another store?” Components Stores If you discover that there are actually multiple, non- overlapping domains within that store, you have a good case for breaking it into separate stores. - Bill Fisher
  19. Web APIs API Helpers Actions Dispatcher Stores CoMPoNEnTS User interface

    IN (from Stores) Change events Model data OUT (to Actions) Calls w/ UI data Components
  20. Web APIs API Helpers Actions Dispatcher RoUTeR Matches URL changes

    to code IN (from URL) Requested application state OUT (to Components) URL state Stores Components Router OUT (to Actions) URL state
  21. All together, data flow should be UnIDiREcTIoNAl Web APIs API

    Helpers Actions Dispatcher Stores Components
  22. Let’s look at a couple CrUD Flows - Immediately applicable,

    write a lot of them - Exercise all the parts Examples: a books administration app
  23. FiLE StRUcTUrE books-actions.js books-api.js books-constants.js books-list.js books-store.js books/ common/ api.js

    app-dispatcher.js node_modules/ flux/ react-router/
  24. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders Action
 calls api helper
  25. ShOW Flow Router transitions Store saves data, emits change Action


    calls api helper Api Helper calls api, success action Action dispatches Component gets store data, renders 1 Router.run(routes, (Handler, state) => { 2 React.render(<Handler />, document.body) 3 BooksAction.transition(state.params) 4 })
  26. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders 1 exports.transition = (filter) => { 2 if (!BooksStore.find(filter)) 3 BooksApi.fetch(null, filter) 4 } Action
 calls api helper
  27. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders 1 exports.fetch = (url, filter) => { 2 request.get(url).end((err, res) => { 3 // .. 4 BooksActions.fetchSuccess(res.body.books) 5 }) 6 } Action
 calls api helper
  28. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders 1 exports.fetchSuccess = (models, filter) => { 2 AppDispatcher.handleServerAction({ 3 type: ActionTypes.FETCH_SUCCESS, 4 models: models, 5 filter: filter 6 }) 7 } Action
 calls api helper
  29. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders 1 BooksStore.dispatchToken = AppDispatcher.register((payload) => { 2 var action = payload.action 3 switch(action.type) { 4 5 case ActionTypes.FETCH_SUCCESS: 6 cache(action.models) 7 BooksStore.emitChange() 8 break Action
 calls api helper
  30. ShOW Flow Router transitions Store saves data, emits change Api

    Helper calls api, success action Action dispatches Component gets store data, renders 1 componentDidMount() { 2 BooksStore.addChangeListener(this._onChange) 3 }, 4 5 _onChange() { 6 this.setState({ 7 book: BooksStore.find({ id: this.getParams().id }) 8 }) 9 }, Action
 calls api helper
  31. CrEAtE PaGE Flow Router transitions Component gets store data renders

    Component calls create select action Action dispatches Store inits data, emits change
  32. CrEAtE PaGE Flow Router transitions Component gets store data renders

    Component calls create select action Action dispatches Store inits data, emits change 1 Router.run(routes, (Handler, state) => { 2 React.render(<Handler />, document.body) 3 BooksAction.transition(state.params) 4 })
  33. CrEAtE PaGE Flow Router transitions Component gets store data renders

    Component calls create select action Action dispatches Store inits data, emits change 1 componentDidMount() { 2 BooksActions.createSelect() 3 }
  34. CrEAtE PaGE Flow Router transitions Component gets store data renders

    Component calls create select action Action dispatches Store inits data, emits change 1 exports.createSelect = () => { 2 AppDispatcher.handleViewAction({ 3 type: ActionTypes.CREATE_SELECT 4 }) 5 }
  35. CrEAtE PaGE Flow Router transitions Component gets store data renders

    Component calls create select action Action dispatches Store inits data, emits change 1 BooksCreateStore.dispatchToken = AppDispatcher.register((payload) => { 2 var action = payload.action 3 switch(action.type) { 4 5 case ActionTypes.CREATE_SELECT: 6 _book = {} 7 break
  36. CrEAtE PaGE Flow Router transitions Component calls create select action

    Action dispatches Store inits data, emits change 1 componentDidMount() { 2 BooksStore.addChangeListener(this._onChange) 3 }, 4 5 _onChange() { 6 this.setState({ 7 book: BooksCreateStore.getBook() 8 }) 9 }, Component gets store data renders
  37. CrEAtE SaVE Flow Component handles submit, create action Action calls

    api helper Api Helper calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions
  38. CrEAtE SaVE Flow Component handles submit, create action Action calls

    api helper Api Helper calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 onSubmit(evt) { 2 evt.preventDefault() 3 BooksActions.create({ 4 title: React.findDOMNode(this.refs.title).value, 5 // ... 6 }) 7 },
  39. CrEAtE SaVE Flow Component handles submit, create action Action calls

    api helper Api Helper calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 exports.create = (model) => { 2 BooksApi.create(model) 3 }
  40. CrEAtE SaVE Flow Component handles submit, create action Api Helper

    calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 exports.create = (url, model) => { 2 request.post(url).send({ books: model }) 3 .end((err, res) => { 4 // .. 5 BooksActions.createSuccess(res.body.books) 6 }) 7 } Action calls api helper
  41. CrEAtE SaVE Flow Component handles submit, create action Api Helper

    calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 exports.createSuccess = (model) => { 2 AppDispatcher.handleServerAction({ 3 type: ActionTypes.CREATE_SUCCESS, 4 model: model 5 }) 6 } Action calls api helper
  42. CrEAtE SaVE Flow Component handles submit, create action Api Helper

    calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 BooksCreateStore.dispatchToken = AppDispatcher.register((payload) => { 2 var action = payload.action 3 switch(action.type) { 4 5 case ActionTypes.CREATE_SUCCESS: 6 _book = action.model 7 _isCreated = true 8 BooksCreateStore.emitChange() 9 break Action calls api helper
  43. CrEAtE SaVE Flow Component handles submit, create action Api Helper

    calls api ,success action Action dispatches Store saves data/flag, emits change Component gets store data, renders, transitions 1 componentDidMount() { 2 BooksCreateStore.addChangeListener(this._onChange) 3 }, 4 5 _onChange() { 6 this.setState(this.getStateFromStores(), () => { 7 if (BooksCreateStore.isCreated()) { 8 Router.transitionTo('books-show', { id: this.state.book.id }) 9 } 10 }) 11 }, Action calls api helper
  44. AnECdOTeS from my use of the pattern…

  45. CoMPoNEnTS - So clean - Just views again - Feels

    like all the right stuff has been moved out - Stores make it easier to state in more places
  46. AcTIoNS - Sometimes seems like a superfluous layer - Has

    value in consistency - Helps with Law of Demeter - Nice translation from method calls (good in views) to dispatch calls (knowledge of dispatchers and action types)
  47. StOReS - Sync methods are awesome — can assume the

    data is there — makes view usage of stores clean - `waitFor` is helpful — great for dependent data in separate logical stores - Love not having setters on Store and letting data mutation flow through dispatcher callback
  48. CoMPlEXiTY - There are many parts. Some of the simplicity/

    beauty of the React component abstraction is diminished in the fray - Lots of code in multiple files — mostly declarative, ok to reason about by itself, harder to see connections
  49. DaTA FlOW - Generally feels like single direction. Once the

    data is signaled to be available in view, it’s consistent - Still sometimes a challenge to follow the data previous to that
  50. EvENtS - Harder to follow source of actions/data and know

    what’s affected - Requires consistency in a pattern - Multiple event sets to track 1) Stores register callbacks for dispatcher calls 2) Views register as listeners to store events
  51. ApPRoACh - Experiment with `isCreated` flags. Feels clunky
 - Remove

    metadata (eg, paging) from Stores; Actions won’t need to talk to Stores - Some of my own abstractions for Actions and Stores - Play with some of the Flux implementations How I might experiment with my
  52. LeARnINg CuRVe - More full-featured, real-world examples continue to be

    made - Feels like Backbone days — figure out how Flux works well for you - Relay/GraphQL are incoming
  53. OtHEr FlUX LiBS - Fluxxor - Reflux - Delorean -

    Fluxxy - marty.js - Fluxible - …check HN Generally clean up some boilerplate, abstract a few things behind a smoother interface
  54. Videos / Tuts - facebook.github.io/flux - docs, examples, blog, vids

    - egghead.io/series/learn-react-flux - walkthrough vid Comparisons - flux implementation sites - compare/contrast blog - github.com/yhagio/learn-react-flux - compilation - github.com/voronianski/flux-comparison - impls LeARnINg ReSOuRCeS
  55. ThANk yOU github.com/jaketrent/jaketrent-admin - admin crud github.com/jaketrent/jaketrent-books - simple consumer

    CoDE SlIDeS speakerdeck.com/jaketrent/flux-at-1-dot-21-gigawatts