Slide 1

Slide 1 text

7 months in react: The perilous art of swimming upstream

Slide 2

Slide 2 text

Brandon Hays Head of Engineering @tehviking

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

gonna have a bad time

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Tactical Alignment Philosophical alignment vs

Slide 7

Slide 7 text

Tactical Alignment: Short term, unlocks biz value

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

a mismatch in either: you’re swimming upstream

Slide 10

Slide 10 text

When you realize you’re swimming upstream Fight on?

Slide 11

Slide 11 text

When you realize you’re swimming upstream pivot?

Slide 12

Slide 12 text

When you realize you’re swimming upstream retreat?

Slide 13

Slide 13 text

Story 1: The Legend of Dhh

Slide 14

Slide 14 text

ACT I: OUR HERO VENTURES FORTH

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

why configure xml when you could do puts debugging instead?

Slide 18

Slide 18 text

founds the Church of the 80% use case

Slide 19

Slide 19 text

the hero we need to slay the J2EE dragon

Slide 20

Slide 20 text

ACT II: the elements conspire

Slide 21

Slide 21 text

DHH realizes he is swimming upstream

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

ACT III: our hero perseveres

Slide 25

Slide 25 text

DHH, staying on message DEAL WITH IT

Slide 26

Slide 26 text

the Church of the 80% Use Case continues to stand

Slide 27

Slide 27 text

Story 2: Yehuda’s A wakening

Slide 28

Slide 28 text

ACT I: OUR HERO VENTURES FORTH

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

new church: Our Lady of Shared Abstractions

Slide 34

Slide 34 text

ACT II: the elements conspire

Slide 35

Slide 35 text

yehuda is chased out

Slide 36

Slide 36 text

yehuda glimpses the future of the web

Slide 37

Slide 37 text

not everyone likes yehuda’s vision

Slide 38

Slide 38 text

ACT III: our hero Pivots

Slide 39

Slide 39 text

yehuda pivots, hard

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Story 3: The Adventure of Freeman

Slide 43

Slide 43 text

ACT I: OUR HERO VENTURES FORTH

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Yay! Time to design our very own framework!

Slide 48

Slide 48 text

Stack layer 1: Build tooling

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Don’t eject!

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Don’t eject!

Slide 53

Slide 53 text

definitely Eject!

Slide 54

Slide 54 text

do not stay inside the nice warm cabin!

Slide 55

Slide 55 text

this seemed... weird

Slide 56

Slide 56 text

React + Webpack build layer

Slide 57

Slide 57 text

import React from 'react';
 
 export const App = ({text, initData}) => (


Hello, the text is {text}


 Load Data


 )
 
 export default App;
 Hello, world! components/app.jsx

Slide 58

Slide 58 text

After “hello world” in React

Slide 59

Slide 59 text

the first fork in the road: Routing

Slide 60

Slide 60 text

Stack layer 2: routing J/K LOL

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Stack layer 2: state management

Slide 65

Slide 65 text

this is a big decision

Slide 66

Slide 66 text

redux: forbidden

Slide 67

Slide 67 text

redux: mandatory

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Stack layer 3: routing

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

It’s almost like a real webapp!

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

import app from './app';
 import things from './things';
 import vs from './vs';
 
 export default combineReducers({
 app,
 things,
 vs
 }); our first reducer reducers/index.js

Slide 78

Slide 78 text

Stack layer 4: data fetching

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

the “paved road” ends here

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

What about loading and saving data on route transitions?

Slide 85

Slide 85 text

Stack layer 5: reaction and side effects

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

our use of thunk, in approximately real time

Slide 88

Slide 88 text

truth in advertising: redux-thunk edition

Slide 89

Slide 89 text

state management libraries in react

Slide 90

Slide 90 text

“saga” just sounds cooler

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

The air’s getting thin at this level of abstraction

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

Stack layer 6: models

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

ACT II: the elements conspire

Slide 102

Slide 102 text

Stack layer 6: Testing

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

Wait... what about the UI?

Slide 107

Slide 107 text

We realize: we’re swimming upstream

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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!)

Slide 110

Slide 110 text

Your app is a snowflake of interwoven dependencies

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

this dependency tree is not free... literally

Slide 113

Slide 113 text

cost 1: Weak & fragile routing mishmash

Slide 114

Slide 114 text

cost 2: Inconsistent data loading & access in models

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

ACT III: our hero retreats

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

rewrites are dangerous

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

can we prove the concept in 3 days?

Slide 124

Slide 124 text

rewrite unlocked

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

rewrites are not easy to pull off

Slide 127

Slide 127 text

how it felt

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

how did we wind up in this position?

Slide 131

Slide 131 text

Basically, I didn’t listen

Slide 132

Slide 132 text

• Persevere pivot Retreat you’re swimming upstream!

Slide 133

Slide 133 text

when to persevere

Slide 134

Slide 134 text

Low philosophical fit, high tactical fit

Slide 135

Slide 135 text

when to pivot

Slide 136

Slide 136 text

high philosophical fit, low tactical fit

Slide 137

Slide 137 text

when to pack it in

Slide 138

Slide 138 text

low philosophical fit, low tactical fit

Slide 139

Slide 139 text

aka when to do the big scary rewrite

Slide 140

Slide 140 text

High philosophical fit, high tactical fit

Slide 141

Slide 141 text

my dream: a better-aligned version of react

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

º 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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

OH MY GOSH JUST USE EMBER

Slide 146

Slide 146 text

by Uncle Bill Williams Tactical alignment: $$$

Slide 147

Slide 147 text

Lots of folks in Ruby are pivoting right now

Slide 148

Slide 148 text

Where are we going?

Slide 149

Slide 149 text

The ruby community gave us many gifts

Slide 150

Slide 150 text

Our job is to pass the torch to the next generation

Slide 151

Slide 151 text

the react story has a happy ending

Slide 152

Slide 152 text

want to get involved? talk to @cowboyd

Slide 153

Slide 153 text

remember the gifts you’ve been given

Slide 154

Slide 154 text

@tehviking THANKS!