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. tactical goals º Ship & promote Basecamp º Build and

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

    º Solve the 80% use case for people º Use and build tools that make programmers happy
  3. 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
  4. philosophical goals º Build and nurture healthy communities º Push

    the web as a platform by improving tools º Promote & build on the idea of shared abstractions
  5. 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
  6. philosophical goals º Choose & participate in a healthy community

    º Build trusted systems using “Outside-in” testing º Build on shared abstractions
  7. 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
  8. React- Router v3 redux-little- router React- Router v4 Junctions Router5

    MobX Redux setState() React + Webpack state management layer
  9. MobX React- Router v3 Redux setState() redux-little- router React- Router

    v4 Junctions Router5 React + Webpack state management layer
  10. 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
  11. 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
  12. 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
  13. 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
  14. import app from './app';
 import things from './things';
 import vs

    from './vs';
 
 export default combineReducers({
 app,
 things,
 vs
 }); our first reducer reducers/index.js
  15. MobX React- Router v3 Redux setState() redux-little- router React- Router

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

    v4 Junctions Router5 Superagent Axios fetch() Request React + Webpack data access layer
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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!)
  28. 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
  29. OK, last time, ember is the right call. I feel

    like I’m taking crazy pills!
  30. 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
  31. 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
  32. philosophical goals º Choose & participate in a healthy community

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

    wrong any longer” - Me, definitely
  34. 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
  35. 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
  36. 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
  37. 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
  38. º 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