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

Seven months in React: The perilous art of swimming upstream

tehviking
September 14, 2017

Seven months in React: The perilous art of swimming upstream

You chose to use your tools because something about them spoke to you. But there’s also tremendous social pressure to constantly learn and migrate to new technologies, for reasons you may not even agree with. You also have the added strain of trying to ship high-quality software on tight timelines. So how do you know when do you fight to swim upstream and when to go with the flow?

We’ll use the example of journeying into React, as well as stories from folks who have made significant contributions to our industry, to illustrate the empowering concepts of “tactical alignment” versus “philosophical alignment”. Armed with this knowledge, you can sidestep many existential crises that come with our ever-changing landscape and focus on shipping great work.

tehviking

September 14, 2017
Tweet

More Decks by tehviking

Other Decks in Programming

Transcript

  1. 7 months in react:
    The perilous
    art of swimming
    upstream

    View full-size slide

  2. Brandon Hays
    Head of Engineering
    @tehviking

    View full-size slide

  3. gonna have a
    bad time

    View full-size slide

  4. Rails new? I haven’t heard
    that command in years.

    View full-size slide

  5. Tactical
    Alignment
    Philosophical
    alignment
    vs

    View full-size slide

  6. Tactical
    Alignment:
    Short term,
    unlocks
    biz value

    View full-size slide

  7. Philosophical
    Alignment:
    Long term,
    tied to the
    reasons you
    do the work

    View full-size slide

  8. a mismatch in either:
    you’re swimming
    upstream

    View full-size slide

  9. When you realize you’re
    swimming upstream
    Fight on?

    View full-size slide

  10. When you realize you’re
    swimming upstream
    pivot?

    View full-size slide

  11. When you realize you’re
    swimming upstream
    retreat?

    View full-size slide

  12. Story 1:
    The Legend of Dhh

    View full-size slide

  13. ACT I:
    OUR HERO
    VENTURES FORTH

    View full-size slide

  14. tactical goals
    º Ship & promote Basecamp
    º Build and ship database-
    backed apps quickly
    º Gather a community to
    help promote and grow Rails

    View full-size slide

  15. philosophical goals
    º Grow usage of OSS tooling,
    Ruby specifically
    º Solve the 80% use case
    for people
    º Use and build tools that
    make programmers happy

    View full-size slide

  16. why
    configure
    xml when
    you could
    do puts
    debugging
    instead?

    View full-size slide

  17. founds the Church
    of the 80% use case

    View full-size slide

  18. the hero we
    need to slay
    the J2EE
    dragon

    View full-size slide

  19. ACT II:
    the elements
    conspire

    View full-size slide

  20. DHH realizes he is
    swimming upstream

    View full-size slide

  21. “Building
    webapps in
    Rails isn't
    programming!”
    “nice monolith,
    ya dingus!”

    View full-size slide

  22. ACT III:
    our hero
    perseveres

    View full-size slide

  23. DHH,
    staying
    on
    message
    DEAL WITH IT

    View full-size slide

  24. the Church of the
    80% Use Case
    continues to stand

    View full-size slide

  25. Story 2:
    Yehuda’s A
    wakening

    View full-size slide

  26. ACT I:
    OUR HERO
    VENTURES FORTH

    View full-size slide

  27. tactical goals
    º Improve community
    participation for OSS via
    documentation
    º Learn new technologies
    and find ways to improve
    them
    º Build and ship real apps
    with great dev tooling

    View full-size slide

  28. philosophical goals
    º Build and nurture healthy
    communities
    º Push the web as a
    platform by improving tools
    º Promote & build on the
    idea of shared abstractions

    View full-size slide

  29. Helps forge the Sword of
    Merb and does mighty
    battle with Rails

    View full-size slide

  30. new church:
    Our Lady of Shared
    Abstractions

    View full-size slide

  31. ACT II:
    the elements
    conspire

    View full-size slide

  32. yehuda is chased out

    View full-size slide

  33. yehuda glimpses the
    future of the web

    View full-size slide

  34. not everyone likes
    yehuda’s vision

    View full-size slide

  35. ACT III:
    our hero
    Pivots

    View full-size slide

  36. yehuda pivots, hard

    View full-size slide

  37. Story 3:
    The Adventure of
    Freeman

    View full-size slide

  38. ACT I:
    OUR HERO
    VENTURES FORTH

    View full-size slide

  39. tactical goals
    º Build and ship a nice SPA
    quickly
    º Stay on paved roads
    (good docs, etc.)
    º Use popular, easy-to-
    hire-for tooling
    º Learn novel new tools &
    techniques

    View full-size slide

  40. philosophical goals
    º Choose & participate in a
    healthy community
    º Build trusted systems
    using “Outside-in” testing
    º Build on shared
    abstractions

    View full-size slide

  41. cool, cool...
    Quick question:
    why not Ember?

    View full-size slide

  42. Yay! Time to
    design our
    very own
    framework!

    View full-size slide

  43. Stack
    layer 1:
    Build
    tooling

    View full-size slide

  44. React +
    Webpack
    $ yarn global add create-react-app
    $ create-react-app myapp
    build layer

    View full-size slide

  45. Don’t eject!

    View full-size slide

  46. Let’s stay inside the
    nice warm cabin, ok?

    View full-size slide

  47. Don’t eject!

    View full-size slide

  48. definitely Eject!

    View full-size slide

  49. do not stay inside the
    nice warm cabin!

    View full-size slide

  50. this seemed... weird

    View full-size slide

  51. React +
    Webpack
    build layer

    View full-size slide

  52. import React from 'react';


    export const App = ({text, initData}) => (


    Hello, the text is {text}

    Load Data


    )


    export default App;

    Hello, world!
    components/app.jsx

    View full-size slide

  53. After “hello
    world” in React

    View full-size slide

  54. the first fork
    in the road:
    Routing

    View full-size slide

  55. Stack
    layer 2:
    routing
    J/K LOL

    View full-size slide

  56. React-
    Router v3
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    React +
    Webpack
    Routing layer

    View full-size slide

  57. Unskippable
    side quest
    alert!
    find a state
    mgmt library
    before you
    can route.

    View full-size slide

  58. React-
    Router v3
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    MobX
    Redux
    setState()
    React +
    Webpack
    state management layer

    View full-size slide

  59. Stack
    layer 2:
    state
    management

    View full-size slide

  60. this is a big decision

    View full-size slide

  61. redux: forbidden

    View full-size slide

  62. redux: mandatory

    View full-size slide

  63. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    React +
    Webpack
    state management layer

    View full-size slide

  64. And redux is actually
    pretty cool
    ...Just not
    ergonomically sound

    View full-size slide

  65. Weird thing 2: why does
    the routing dependency
    tree seem backwards?

    View full-size slide

  66. Stack
    layer 3:
    routing

    View full-size slide

  67. import React from 'react';

    import Things from ‘./Things';

    import VsContainer from ‘../containers/VsContainer';

    import { RelativeFragment as Fragment, Link } from 'redux-little-router';


    export const App = ({ text, initData, listings }) => (




    Show Things

    VS App











    )
    Let’s get routing!
    components/app.jsx

    View full-size slide

  68. It’s almost like a
    real webapp!

    View full-size slide

  69. import { connect } from 'react-redux';

    import Vs from '../components/Vs';


    const mapStateToProps = state => ({

    questions: state.vs.questions,

    currentQuestion: state.currentQuestion

    });


    export default connect(mapStateToProps)(Vs);
    our first Container
    containers/vsContainer.jsx

    View full-size slide

  70. import React from 'react';

    import VsItemPair from './VsItemPair';


    const Vs = ({ questions }) => {

    return (




    Hello this is the vs app





    );

    };


    export default Vs;
    our first component
    components/vs.jsx

    View full-size slide

  71. import questions from ‘../assets/vs.json';


    const initialState = {

    questions: questions.entries,

    currentQuestion: 0

    };


    export default (state = initialState, action) => {

    switch(action.type) {

    default:

    return state;

    }

    }
    our first reducer
    reducers/vs.js

    View full-size slide

  72. import app from './app';

    import things from './things';

    import vs from './vs';


    export default combineReducers({

    app,

    things,

    vs

    });
    our first reducer
    reducers/index.js

    View full-size slide

  73. Stack
    layer 4:
    data
    fetching

    View full-size slide

  74. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    React +
    Webpack
    data access layer

    View full-size slide

  75. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    React +
    Webpack
    data access layer

    View full-size slide

  76. the “paved road”
    ends here

    View full-size slide

  77. import axios from 'axios';

    <...requestData() goes here>

    export function loadData(data) {

    return {

    type: 'LOAD_DATA',

    data

    };

    };


    export function getDataFromServer() {

    return dispatch => {

    dispatch(requestData());

    return axios.get('/api').then(

    res => dispatch(loadData(res.data)));

    };

    };
    our first action
    actions/index.js

    View full-size slide

  78. import React from 'react';

    import Things from ‘./Things';

    import VsContainer from ‘../containers/VsContainer';

    import { RelativeFragment as Fragment, Link } from 'redux-little-router';


    export const App = ({ text, initData, listings, getDataFromServer }) => (




    Show Things

    VS App
    Load Data From Server











    )
    load some data!
    components/app.jsx

    View full-size slide

  79. What about
    loading and
    saving data
    on route
    transitions?

    View full-size slide

  80. Stack
    layer 5:
    reaction
    and side
    effects

    View full-size slide

  81. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    reactive layer

    View full-size slide

  82. our use of thunk,
    in approximately
    real time

    View full-size slide

  83. truth in advertising:
    redux-thunk edition

    View full-size slide

  84. state management
    libraries in react

    View full-size slide

  85. “saga” just
    sounds cooler

    View full-size slide

  86. import axios from 'axios';

    import { takeEvery, put } from 'redux-saga/effects';

    import * as actions from '../actions';


    function* dispatchAPICall(action) {

    if (action.payload.pathname === '/things') {

    let response = yield axios

    .get('/api/things')

    .then(res => res.data);

    yield put(actions.loadThings(response));

    }

    }


    export default function* watchRouter() {

    yield takeEvery('ROUTER_LOCATION_CHANGED', dispatchAPICall)

    }

    our first saga
    sagas/transition.js

    View full-size slide

  87. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    reactive layer

    View full-size slide

  88. The air’s getting
    thin at this level
    of abstraction

    View full-size slide

  89. Just checking
    in... You said
    there was a
    good reason
    *not* to use
    Ember, right?

    View full-size slide

  90. “don’t change horses
    mid-stream”
    - Some president,
    probably

    View full-size slide

  91. Stack
    layer 6:
    models

    View full-size slide

  92. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    Model layer

    View full-size slide

  93. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    Model layer

    View full-size slide

  94. import { Record, Map, List, fromJS } from 'immutable';

    import Details from './details';


    const Thing = Record({

    id: 0,

    externalId: '',

    thingUrl: '',

    status: '',

    details: Details(),

    attributes: Map(),

    images: List(),

    isFavorite: false,

    isBanned: false

    });


    export default { Thing };
    our first model
    ducks/things/models.js

    View full-size slide

  95. but something’s
    not right...
    that’s our
    framework folks!

    View full-size slide

  96. ACT II:
    the elements
    conspire

    View full-size slide

  97. Stack
    layer 6:
    Testing

    View full-size slide

  98. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    testing layer
    Jest +
    Enzyme
    Nightmare
    Karma +
    Mocha
    cypress.io
    TestCafe
    Selenium

    View full-size slide

  99. Let’s use an off-the-
    shelf acceptance
    test solution

    View full-size slide

  100. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    testing layer
    Jest +
    Enzyme
    Nightmare
    Karma +
    Mocha
    cypress.io
    TestCafe
    Selenium

    View full-size slide

  101. Wait... what
    about the UI?

    View full-size slide

  102. We realize: we’re
    swimming upstream

    View full-size slide

  103. our hero comes
    crawling back to
    Karma
    Look who’s come
    crawling back
    to karma

    View full-size slide

  104. var webpack = require('webpack');


    module.exports = function (config) {

    config.set({

    browsers: [ 'Chrome' ], //run in Chrome

    singleRun: true, //just run once by default

    frameworks: [ 'mocha' ], //use the mocha test framework

    files: [

    'tests.webpack.js' //just load this file

    ],

    plugins: [ 'karma-chrome-launcher', 'karma-chai', 'karma-mocha',

    'karma-sourcemap-loader', 'karma-webpack', 'karma-coverage',

    'karma-mocha-reporter'

    ],

    preprocessors: {

    'tests.webpack.js': [ 'webpack', 'sourcemap' ] //preprocess with webpack and our sourcemap loader

    },

    reporters: [ 'mocha', 'coverage' ], //report results in this format

    webpack: { //kind of a copy of your webpack config

    devtool: 'inline-source-map', //just do inline source maps instead of the default

    module: {

    loaders: [

    { test: /\.js$/, loader: 'babel-loader' }

    ],

    postLoaders: [ { //delays coverage til after tests are run, fixing transpiled source coverage error

    test: /\.js$/,

    exclude: /(test|node_modules|bower_components)\//,

    loader: 'istanbul-instrumenter' } ]

    }

    },

    webpackServer: {

    noInfo: true //please don't spam the console when running in karma!

    },

    coverageReporter: {

    type: 'html', //produces a html document after code is run

    dir: 'coverage/' //path to created html doc

    }

    });

    };
    configure karma (whee!)

    View full-size slide

  105. Your app is a
    snowflake of
    interwoven
    dependencies

    View full-size slide

  106. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    our framework
    Jest +
    Enzyme
    Nightmare
    Karma +
    Mocha
    cypress.io
    TestCafe
    Selenium

    View full-size slide

  107. this dependency
    tree is not free...
    literally

    View full-size slide

  108. cost 1: Weak &
    fragile routing
    mishmash

    View full-size slide

  109. cost 2:
    Inconsistent data
    loading & access
    in models

    View full-size slide

  110. cost 3: $*!$%^&$
    acceptance testing

    View full-size slide

  111. ACT III:
    our hero
    retreats

    View full-size slide

  112. OK, last time,
    ember is the
    right call.
    I feel like I’m
    taking crazy
    pills!

    View full-size slide

  113. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    our framework graph
    Jest +
    Enzyme
    Nightmare
    Karma +
    Mocha
    cypress.io
    TestCafe
    Selenium

    View full-size slide

  114. rewrites are
    dangerous

    View full-size slide

  115. tactical goals
    º Build and ship a nice SPA
    quickly
    º Stay on paved roads
    (good docs, etc.)
    º Use popular, easy-to-
    hire-for tooling
    º Learn novel new tools &
    techniques

    View full-size slide

  116. philosophical goals
    º Choose & participate in a
    healthy community
    º Build trusted systems
    using “Outside-in” testing
    º Build on shared
    abstractions

    View full-size slide

  117. “I guess I’d rather be
    late being right than be
    wrong any longer”
    - Me, definitely

    View full-size slide

  118. can we prove the concept in 3 days?

    View full-size slide

  119. rewrite unlocked

    View full-size slide

  120. import { describe, it, beforeEach, afterEach } from 'mocha';

    import { expect } from 'chai';

    import startApp from 'consumer-ui/tests/helpers/start-app';

    import destroyApp from 'consumer-ui/tests/helpers/destroy-app';

    import { select } from "consumer-ui/tests/helpers/x-select";


    describe('Acceptance | tune preferences', function() {

    let application;


    beforeEach(function() {

    application = startApp();

    });


    afterEach(function() {

    destroyApp(application);

    });


    describe('when visiting the homefit route', function() {

    beforeEach(function() {

    server.loadFixtures('users');

    visit('/homefit?userID=71ddc3f0-02b9-4b3c-959f-e81679760e9');

    });


    it('redirects you to the tune preferences page', function() {

    expect(currentURL()).to.equal('/homefit/preferences/tune');

    });


    it('loads your existing preferences', function() {

    expect($("[data-test-preference-select-area] option:selected").length).to.equal(1);

    expect($("[data-test-preference-select-area] option:selected:first").text()).to.equal("Downtown Austin");

    });


    it('has no existing min or max price options', function() {

    expect($("[data-test-preference-select-minprice] option:selected").text()).to.equal("None");

    expect($("[data-test-preference-select-maxprice] option:selected").text()).to.equal("None");

    glorious tests!
    tests/acceptance/preferences-test.js

    View full-size slide

  121. rewrites are not
    easy to pull off

    View full-size slide

  122. MobX
    React-
    Router v3
    Redux
    setState()
    redux-little-
    router
    React-
    Router v4
    Junctions
    Router5
    Superagent
    Axios
    fetch()
    Request
    Redux-
    Thunk
    Redux Saga
    Redux-CRUD
    Observable
    React +
    Webpack
    mori
    POJOs
    Transis
    Immutable.js
    Swarm.js
    our framework graph
    Jest +
    Enzyme
    Nightmare
    Karma +
    Mocha
    cypress.io
    TestCafe
    Selenium

    View full-size slide

  123. Ember-
    Redux
    POJOs
    Ember +
    Ember CLI
    Ember’s framework graph
    Ember
    Data
    router.js fetch() Actions Ember.Object
    Immutable.js
    POJOs
    Mocha/Testem
    qUnit/Testem

    View full-size slide

  124. how did we wind up
    in this position?

    View full-size slide

  125. Basically, I didn’t
    listen

    View full-size slide

  126. • Persevere
    pivot
    Retreat
    you’re swimming upstream!

    View full-size slide

  127. when to persevere

    View full-size slide

  128. Low philosophical
    fit, high tactical
    fit

    View full-size slide

  129. when to pivot

    View full-size slide

  130. high philosophical
    fit, low tactical fit

    View full-size slide

  131. when to
    pack it in

    View full-size slide

  132. low philosophical fit,
    low tactical fit

    View full-size slide

  133. aka when to do the
    big scary rewrite

    View full-size slide

  134. High philosophical
    fit, high tactical fit

    View full-size slide

  135. my dream: a
    better-aligned
    version of react

    View full-size slide

  136. My dream framework:
    Solves 80% SPA use case:
    º Rock-solid, flexible routing layer
    º Model layer with data loading
    º State management with immutable
    data that auto-updates UI layer
    º Simple, inline templating like JSX
    º INTEGRATED ACCEPTANCE TESTING
    tactical goals

    View full-size slide

  137. º Large community focused on
    shared values & solutions
    º Simple to start, easy to learn
    with robust documentation
    º Post-adoption support via
    community resources
    º Testing & deployment: first-class
    citizens
    My dream framework:
    philosophical goals

    View full-size slide

  138. excuse *me*, princess...
    Isn’t that just Ember?

    View full-size slide

  139. OH
    MY
    GOSH
    JUST
    USE
    EMBER

    View full-size slide

  140. by
    Uncle Bill Williams
    Tactical alignment: $$$

    View full-size slide

  141. Lots of folks
    in Ruby are
    pivoting
    right now

    View full-size slide

  142. Where are we going?

    View full-size slide

  143. The ruby community
    gave us many gifts

    View full-size slide

  144. Our job is to pass
    the torch to the
    next generation

    View full-size slide

  145. the react story
    has a happy ending

    View full-size slide

  146. want to get involved?
    talk to @cowboyd

    View full-size slide

  147. remember the gifts
    you’ve been given

    View full-size slide

  148. @tehviking
    THANKS!

    View full-size slide