Cleaning the Tar

Using React in Firefox DevTools

StrangeLoop, 2015 / James Long (@jlongster)

ƒ(state) → UI

demo

React

React: ƒ(state) → UI

  • Single atom app state - ClojureScript
  • Actions - Flux
  • `update` function - Elm

dispatch({
  type: constants.ADD_BREAKPOINT,
  url: "foo.js",
  line: 5
})
          

var initialState = [];

function update(state = initialState, action) {
  switch(action.type) {
  case constants.ADD_BREAKPOINT:
    return [...state, {
      url: action.url,
      line: action.line
    }];
  case constants.REMOVE_BREAKPOINT:
    return state.filter(bp => !isEqual(bp, action));
  }

  return state;
}
          

"highlight var initialState = [];"

function update("highlight state = initialState", action) {
  switch(action.type) {
  case constants.ADD_BREAKPOINT:
    return [...state, {
      url: action.url,
      line: action.line
    }];
  case constants.REMOVE_BREAKPOINT:
    return state.filter(bp => !isEqual(bp, action));
  }

  return state;
}
          

var initialState = [];

function update(state = initialState, action) {
  switch(action.type) {
  "highlight case constants.ADD_BREAKPOINT:
    return [...state, {
      url: action.url,
      line: action.line
    }];"
  case constants.REMOVE_BREAKPOINT:
    return state.filter(bp => !isEqual(bp, action));
  }

  return state;
}
          

function update(state, action) {
  return {
    sources: sourceUpdate(state.sources, action),
    breakpoints: bpUpdate(state.breakpoints, action)
  }
}
          

demo


var initialState = [];

function update(state = initialState, action) {
  switch(action.type) {
  case constants.ADD_BREAKPOINT:
    return [...state, {
      url: action.url,
      line: action.line
    }];
  case constants.REMOVE_BREAKPOINT:
    return state.filter(bp => !isEqual(bp, action));
  }

  return state;
}
          

var initialState = [];

function update(state = initialState, action) {
  switch(action.type) {
  case constants.ADD_BREAKPOINT:
    "highlight return [...state, {
      url: action.url,
      line: action.line
    }];"
  case constants.REMOVE_BREAKPOINT:
    "highlight return state.filter(bp => !isEqual(bp, action));"
  }

  return state;
}
          

var initialState = [];

function update(state = initialState, action, emit) {
  switch(action.type) {
  case constants.ADD_BREAKPOINT:
    var bp = { url: action.url, line: action.line };
    "appear emit('breakpoint-added', bp);"
    return [...state, bp];

  case constants.REMOVE_BREAKPOINT:
    var bp = { url: action.url, line: action.line };
    "appear emit('breakpoint-removed', bp);"
    return state.filter(_ => !isEqual(_, bp));
  }

  return state;
}
          

Sources.prototype.empty = function() {
  // ...
}

Editor.prototype.empty = function() {
  // ...
}

Variables.prototype.empty = function() {
  // ...
}
          

case constants.RELOAD: {
  Object.keys(state.sources).forEach(k => {
    emit('source', state.sources[k]);
  });
  emit('sources', state.sources);

  const selectedSource = state.selectedSource;
  if(selectedSource &&
     state.sourcesText[selectedSource]) {
    const source = state.sources[selectedSource];
    emit('source-selected', source);
    emit('source-selected-ready', {
      source: source,
      opts: state.selectedSourceOpts
    });
  }
}
          

Action Creators


function addBreakpoint(url, line) {
  return {
    type: constants.ADD_BREAKPOINT,
    url: url,
    line: line
  }
}
          

function addBreakpoint(url, line) {
  return (dispatch, getState) => {
    dispatch({
      type: constants.ADD_BREAKPOINT,
      url: url,
      line: line
    });
  };
}
          

function addBreakpoint(url, line) {
  return (dispatch, getState) => {
    const action = {
      type: constants.ADD_BREAKPOINT,
      url: url,
      line: line
    };

    dispatch(merge(action, { status: 'start' }));

    api.addBreakpoint(bp => {
      dispatch(merge(action, { status: 'done',
                               value: bp }));
    });
  };
}          

function addBreakpoint(url, line) {
  return (dispatch, getState) => {
    const action = {
      type: constants.ADD_BREAKPOINT,
      url: url,
      line: line
    };

    "highlight dispatch(merge(action, { status: 'start' }));"

    api.addBreakpoint(bp => {
      "highlight dispatch(merge(action, { status: 'done',
                               value: bp }));"
    });
  };
}          

function addBreakpoint(url, line) {
  return (dispatch, getState) => {
    const action = {
      type: constants.ADD_BREAKPOINT,
      url: url,
      line: line
    };

    dispatch(merge(action, { status: 'start' }));

    api.addBreakpoint().then(bp => {
      // Take my life, BUT YOU'LL NEVER TAKE MY ERRORS
      "highlight setTimeout(() =>  dispatch(...), 0);"
    }).catch(...);
  };
}	      

Om, Elm Effects, Relay, Falcor

Testing

State Transitions


state = update(null, { type: 'init' });
assert(state.x, 0);

state = update({ x: 5 }, { type: 'increment-x' });
assert(state.x, 6);
          

React Components


var TestUtils = React.addons.TestUtils;
var renderer = TestUtils.createRenderer();
var sources = require('source-list');

renderer.render(React.createElement(sources, {
  sources: ['foo.js', 'bar.js', 'baz.js']
}));

var output = renderer.getRenderOutput();
assert(output.type, 'div');
assert(output.props.children.length, 3);
          
  • Minimal async
  • Minimal DOM interaction
  • Pure functions = very fast and easy tests!

Idea?

TODO

  • Migrating to React
  • Immutable.js, typed-immutable, or seamless-immutable
  • Hot Reloading

Challenges

  • Component local state
  • Abstracting data queries

Thanks!

@jlongster