$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

    View Slide

  2. What is
    FlUX
    A pattern for organizing
    state in your React.js apps
    ?

    View Slide

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

    View Slide

  4. Where does the
    1.21 gIGaWAtTS
    Included:
    Dispatcher
    Invariant
    come from?
    Your Code:
    Stores
    Actions
    ApiHelper
    Components
    Router

    View Slide

  5. PlUToNIuM
    All you need is a little
    The parts of Flux…

    View Slide

  6. Web APIs API Helpers Actions
    Dispatcher
    Stores
    Components
    ThE CoDE CoNCePTs
    DaTA FlOW
    and

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  12. Web APIs API Helpers
    Dispatcher
    Stores
    AcTIoNS
    What the app can *do*
    Components
    Actions
    • Wrapped action payload makes all dispatched
    methods consistent

    View Slide

  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

    View Slide

  14. Web APIs API Helpers Actions Stores
    DiSPaTChER
    Global message broker
    Components
    Dispatcher
    • Dispatches all payloads to all stores

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  21. All together, data flow should be
    UnIDiREcTIoNAl
    Web APIs API Helpers Actions
    Dispatcher
    Stores
    Components

    View Slide

  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

    View Slide

  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/

    View Slide

  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

    View Slide

  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(, document.body)
    3 BooksAction.transition(state.params)
    4 })

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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(, document.body)
    3 BooksAction.transition(state.params)
    4 })

    View Slide

  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 }

    View Slide

  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 }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 },

    View Slide

  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 }

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  44. AnECdOTeS
    from my use of the pattern…

    View Slide

  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

    View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide