Busconf 2017

Busconf Icon, Apples Bus emoji

my take aways

by Christoph Haefner / @chr1shaefn3r

Overview

  • Intro to Category Theory
  • Imperative vs. Functional
  • Redux Reducer Refactoring (RRR)
  • Spacemacs

Category Theory

Introduction to Category Theory Thumbnail

Imperative vs. Functional

Imperative vs. Functional Programming

Redux Reducer Refactoring (RRR)

Reduce code size by ~27% (99 -> 72 LOC)

Object.assign calls from 8 down to 2

Smell #1: Call another reducer with a new action


const movies = (state = {}, action) => {
  switch(action.type) {
    case 'ADD_MOVIES_VIA_OBJECTS':
      return Object.assign({}, state, action.movies.
        reduce((movies, movieObject) => (
            Object.assign(movies,
            movie(undefined, Actions.addMovie(movieObject)))
          ), {}));
  }
};
            

Smell #2: Fat reducer + Object.assign everywhere


const movieCollection = (state = {
	isLoading: false, sort: 'title', movies: {}
}, action) => {
  switch(action.type) {
    case ADD_MOVIECOLLECTION_WITH_MOVIES_LINE_BY_LINE:
      return Object.assign({}, state, {
        name: action.movieCollection.name,
        movies: movies(undefined, {
          type: 'ADD_MOVIES_LINE-BY-LINE',
          movies: action.movieCollection.movies
        })
      });
    case ADD_MOVIECOLLECTION_BY_FILE_START:
      return Object.assign({}, state, {
        name: action.movieCollection.name,
        isLoading: true
      });
  }
};
            

This reducer alone 35 LOC!
(two more case statements)

Fix #1: Use reducer for every single attribute


const movieCollection = combineReducers({
  isLoading,
  sort,
  movies,
  name
});
			

Fix #1: No need for Object Assign. (before)


case ADD_MOVIECOLLECTION_BY_FILE_START:
  return Object.assign({}, state, {
    name: action.movieCollection.name,
    isLoading: true
  });
			

Fix #1: No need for Object Assign. (after)


case ADD_MOVIECOLLECTION_BY_FILE_START: 
  return action.movieCollection.name;
			

Fix #1: Makes it perfectly clear if attributes are not used, yet.


const sort = (state = 'title', action) => {
  return state;
}
			

Fix #1: Makes it perfectly clear if attributes are not used, yet. (short)


const sort = (state = 'title', action) => state;
			

Fix #2: Create reducer from map


export default function createReducer(initialState, handlers) {
  return (state = initialState, action) => {
    handlers.hasOwnProperty(action.type)
      ? handlers[action.type](state, action)
      : state;
  }
}
			

Fix #2: Before


const isLoading = (state = false, action) => {
  switch(action.type) {
    case ADD_MOVIECOLLECTION_BY_FILE_START:
      return true;
    case UPDATE_MOVIECOLLECTION_BY_FILE:
      return true;
    case ADD_MOVIECOLLECTION_BY_FILE_FINISHED:
      return false;
  }
  return state;
};
			

Fix #2: After


const isLoading = createReducer(INITIAL_STATE.isLoading, {
  [ADD_MOVIECOLLECTION_BY_FILE_START]:    () => true,
  [UPDATE_MOVIECOLLECTION_BY_FILE]:       () => true,
  [ADD_MOVIECOLLECTION_BY_FILE_FINISHED]: () => false
});
			

Lessons learned

  • Combine Reducers even for single attributes
  • Object.assign is not needed in most cases
  • If you dispatch an action from inside a reducer, you are doing it wrong
  • Use the full power of JavaScript (here: replace switch-case with map)

Spacemacs

Spacemacs

THE END

- Slides: christophhaefner.de/events/busconf17/
- Reveal.js: Source code & documentation