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

Seven months in React: The perilous art of swimming upstream

6fd16b1b6a307ca583526e2ec4dab52d?s=47 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.

6fd16b1b6a307ca583526e2ec4dab52d?s=128

tehviking

September 14, 2017
Tweet

Transcript

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

  2. Brandon Hays Head of Engineering @tehviking

  3. None
  4. gonna have a bad time

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

  6. Tactical Alignment Philosophical alignment vs

  7. Tactical Alignment: Short term, unlocks biz value

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

    the work
  9. a mismatch in either: you’re swimming upstream

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

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

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

  13. Story 1: The Legend of Dhh

  14. ACT I: OUR HERO VENTURES FORTH

  15. tactical goals º Ship & promote Basecamp º Build and

    ship database- backed apps quickly º Gather a community to help promote and grow Rails
  16. philosophical goals º Grow usage of OSS tooling, Ruby specifically

    º Solve the 80% use case for people º Use and build tools that make programmers happy
  17. why configure xml when you could do puts debugging instead?

  18. founds the Church of the 80% use case

  19. the hero we need to slay the J2EE dragon

  20. ACT II: the elements conspire

  21. DHH realizes he is swimming upstream

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

  23. None
  24. ACT III: our hero perseveres

  25. DHH, staying on message DEAL WITH IT

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

  27. Story 2: Yehuda’s A wakening

  28. ACT I: OUR HERO VENTURES FORTH

  29. 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
  30. philosophical goals º Build and nurture healthy communities º Push

    the web as a platform by improving tools º Promote & build on the idea of shared abstractions
  31. Helps forge the Sword of Merb and does mighty battle

    with Rails
  32. None
  33. new church: Our Lady of Shared Abstractions

  34. ACT II: the elements conspire

  35. yehuda is chased out

  36. yehuda glimpses the future of the web

  37. not everyone likes yehuda’s vision

  38. ACT III: our hero Pivots

  39. yehuda pivots, hard

  40. None
  41. None
  42. Story 3: The Adventure of Freeman

  43. ACT I: OUR HERO VENTURES FORTH

  44. 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
  45. philosophical goals º Choose & participate in a healthy community

    º Build trusted systems using “Outside-in” testing º Build on shared abstractions
  46. cool, cool... Quick question: why not Ember?

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

  48. Stack layer 1: Build tooling

  49. React + Webpack $ yarn global add create-react-app $ create-react-app

    myapp build layer
  50. Don’t eject!

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

  52. Don’t eject!

  53. definitely Eject!

  54. do not stay inside the nice warm cabin!

  55. this seemed... weird

  56. React + Webpack build layer

  57. import React from 'react';
 
 export const App = ({text,

    initData}) => (
 <div>
 <h1>Hello, the text is <span className="text">{text}</span></h1>
 <button className="init-data" onClick={initData}>Load Data</button>
 </div>
 )
 
 export default App;
 Hello, world! components/app.jsx
  58. After “hello world” in React

  59. the first fork in the road: Routing

  60. Stack layer 2: routing J/K LOL

  61. React- Router v3 redux-little- router React- Router v4 Junctions Router5

    React + Webpack Routing layer
  62. Unskippable side quest alert! find a state mgmt library before

    you can route.
  63. React- Router v3 redux-little- router React- Router v4 Junctions Router5

    MobX Redux setState() React + Webpack state management layer
  64. Stack layer 2: state management

  65. this is a big decision

  66. redux: forbidden

  67. redux: mandatory

  68. MobX React- Router v3 Redux setState() redux-little- router React- Router

    v4 Junctions Router5 React + Webpack state management layer
  69. And redux is actually pretty cool ...Just not ergonomically sound

  70. Weird thing 2: why does the routing dependency tree seem

    backwards?
  71. Stack layer 3: routing

  72. 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 }) => (
 <div>
 <Fragment forRoute="/">
 <div>
 <Link href="/things">Show Things</Link>
 <Link href="/vs">VS App</Link>
 </div>
 
 <Fragment forRoute="/things">
 <Things things={things} />
 </Fragment>
 <Fragment forRoute="/vs">
 <VsContainer />
 </Fragment>
 </Fragment>
 </div>
 ) Let’s get routing! components/app.jsx
  73. It’s almost like a real webapp!

  74. 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
  75. import React from 'react';
 import VsItemPair from './VsItemPair';
 
 const

    Vs = ({ questions }) => {
 return (
 <div>
 <div className="columns">
 <div className="column">
 <p data-test-description>Hello this is the vs app</p>
 </div>
 </div>
 <VsItemPair pair={questions[0]} />
 </div>
 );
 };
 
 export default Vs; our first component components/vs.jsx
  76. 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
  77. import app from './app';
 import things from './things';
 import vs

    from './vs';
 
 export default combineReducers({
 app,
 things,
 vs
 }); our first reducer reducers/index.js
  78. Stack layer 4: data fetching

  79. MobX React- Router v3 Redux setState() redux-little- router React- Router

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

    v4 Junctions Router5 Superagent Axios fetch() Request React + Webpack data access layer
  81. the “paved road” ends here

  82. 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
  83. 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 }) => (
 <div>
 <Fragment forRoute="/">
 <div>
 <Link href="/things">Show Things</Link>
 <Link href="/vs">VS App</Link> <button className="spec-load-data" onClick={getDataFromServer}>Load Data From Server</button>
 </div>
 
 <Fragment forRoute="/things">
 <Things things={things} />
 </Fragment>
 <Fragment forRoute="/vs">
 <VsContainer />
 </Fragment>
 </Fragment>
 </div>
 ) load some data! components/app.jsx
  84. What about loading and saving data on route transitions?

  85. Stack layer 5: reaction and side effects

  86. 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
  87. our use of thunk, in approximately real time

  88. truth in advertising: redux-thunk edition

  89. state management libraries in react

  90. “saga” just sounds cooler

  91. 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
  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 reactive layer
  93. The air’s getting thin at this level of abstraction

  94. Just checking in... You said there was a good reason

    *not* to use Ember, right?
  95. “don’t change horses mid-stream” - Some president, probably

  96. Stack layer 6: models

  97. 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
  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 Model layer
  99. 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
  100. but something’s not right... that’s our framework folks!

  101. ACT II: the elements conspire

  102. Stack layer 6: Testing

  103. 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
  104. Let’s use an off-the- shelf acceptance test solution

  105. 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
  106. Wait... what about the UI?

  107. We realize: we’re swimming upstream

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

    crawling back to karma
  109. 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!)
  110. Your app is a snowflake of interwoven dependencies

  111. 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
  112. this dependency tree is not free... literally

  113. cost 1: Weak & fragile routing mishmash

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

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

  116. ACT III: our hero retreats

  117. OK, last time, ember is the right call. I feel

    like I’m taking crazy pills!
  118. 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
  119. rewrites are dangerous

  120. 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
  121. philosophical goals º Choose & participate in a healthy community

    º Build trusted systems using “Outside-in” testing º Build on shared abstractions
  122. “I guess I’d rather be late being right than be

    wrong any longer” - Me, definitely
  123. can we prove the concept in 3 days?

  124. rewrite unlocked

  125. 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
  126. rewrites are not easy to pull off

  127. how it felt

  128. 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
  129. 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
  130. how did we wind up in this position?

  131. Basically, I didn’t listen

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

  133. when to persevere

  134. Low philosophical fit, high tactical fit

  135. when to pivot

  136. high philosophical fit, low tactical fit

  137. when to pack it in

  138. low philosophical fit, low tactical fit

  139. aka when to do the big scary rewrite

  140. High philosophical fit, high tactical fit

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

  142. 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
  143. º 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
  144. excuse *me*, princess... Isn’t that just Ember?

  145. OH MY GOSH JUST USE EMBER

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

  147. Lots of folks in Ruby are pivoting right now

  148. Where are we going?

  149. The ruby community gave us many gifts

  150. Our job is to pass the torch to the next

    generation
  151. the react story has a happy ending

  152. want to get involved? talk to @cowboyd

  153. remember the gifts you’ve been given

  154. @tehviking THANKS!