SaltyCrane Blog — Notes on JavaScript and web development

How to map Caps Lock to Escape when tapped and Control when held on Mac OS Sierra

Escape and Control are useful keys when using Vim so it's nice to map them to a more convenient key like Caps Lock. I had been using Karabiner to do this, but Karabiner doesn't work on Mac OS Sierra. Fortunately Karabiner-Elements provides a subset of the features planned for the next generation Karabiner including remapping Caps Lock to Escape when tapped and Control when held down. The solution below is from @zeekay on issue #8. I am using Karabiner-Elements 0.91.12 and macOS Sierra 10.12.4.

  • Uninstall Seil and Karabiner, if previously installed
  • Install Karabiner-Elements
    $ brew cask install karabiner-elements 
    
  • Edit ~/.config/karabiner/karabiner.json to be:
    {
        "global": {
            "check_for_updates_on_startup": true,
            "show_in_menu_bar": true,
            "show_profile_name_in_menu_bar": false
        },
        "profiles": [
            {
                "complex_modifications": {
                    "parameters": {
                        "basic.to_if_alone_timeout_milliseconds": 250
                    },
                    "rules": [
                        {
                            "manipulators": [
                                {
                                    "description": "Change caps_lock to control when used as modifier, escape when used alone",
                                    "from": {
                                        "key_code": "caps_lock",
                                        "modifiers": {
                                            "optional": [
                                                "any"
                                            ]
                                        }
                                    },
                                    "to": [
                                        {
                                            "key_code": "left_control"
                                        }
                                    ],
                                    "to_if_alone": [
                                        {
                                            "key_code": "escape",
                                            "modifiers": {
                                                "optional": [
                                                    "any"
                                                ]
                                            }
                                        }
                                    ],
                                    "type": "basic"
                                }
                            ]
                        }
                    ]
                },
                "devices": [],
                "fn_function_keys": {
                    "f1": "display_brightness_decrement",
                    "f10": "mute",
                    "f11": "volume_decrement",
                    "f12": "volume_increment",
                    "f2": "display_brightness_increment",
                    "f3": "mission_control",
                    "f4": "launchpad",
                    "f5": "illumination_decrement",
                    "f6": "illumination_increment",
                    "f7": "rewind",
                    "f8": "play_or_pause",
                    "f9": "fastforward"
                },
                "name": "Default profile",
                "selected": true,
                "virtual_hid_keyboard": {
                    "caps_lock_delay_milliseconds": 0,
                    "keyboard_type": "ansi"
                }
            }
        ]
    }

Alternative

@kbussell created a script to do the same using Hammerspoon1 instead of Karabiner.


  1. Hammerspoon is awesome.

What are Redux selectors? Why use them?

Selectors are functions that take Redux state as an argument and return some data to pass to the component.

They can be as simple as:

const getDataType = state => state.editor.dataType;

Or they can do more complicated data transformations like filter a list.

They are typically used in mapStateToProps1 in react-redux's connect:

For example this

export default connect(
  (state) => ({
    dataType: state.editor.dataType,
  })
)(MyComponent);

becomes this:

export default connect(
  (state) => ({
    dataType: getDataType(state),
  })
)(MyComponent);

Why?

One reason is to avoid duplicated data in Redux.

Redux state can be thought of like a database and selectors like SELECT queries to get useful data from the database. A good example is a filtered list.

If we have a list of items in Redux but want to show a filtered list of the items in the UI, we could filter the list, store it in Redux then pass it to the component. The problem is there are two copies of some of the items and it is easier for data to get out of sync. If we wanted to update an item, we'd have to update it in two places.

With selectors, a filterBy value would be stored in Redux and a selector would be used to compute the filtered list from the Redux state.

Why not perform the data transformations in the component?

Performing data transformations in the component makes them more coupled to the Redux state and less generic/reusable. Also, as Dan Abramov points out, it makes sense to keep selectors near reducers because they operate on the same state. If the state schema changes, it is easier to update the selectors than to update the components.

Performance

mapStateToProps gets called a lot so performing expensive calculations there is not good. This is where the reselect library comes in. Selectors created with reselects's createSelector will memoize to avoid unnecessary recalculations. Note if performance is not an issue, createSelector is not needed. TODO: add note about shallowCompare.

References / See also

I got interested in selectors after watching Dan Abramov's video: https://egghead.io/lessons/javascript-redux-colocating-selectors-with-reducers

Redux docs on selectors: http://redux.js.org/docs/recipes/ComputingDerivedData.html

Reselect: https://github.com/reactjs/reselect


1. mapStateToProps can be considered a selector itself

git worktree notes

git worktree allows you to have multiple working directories associated with one git repo. It has been useful for me to look at, copy code between, and run two different branches of my code. I learned about git worktreee from James Ide's tweet:

Initial setup

Move the checked out git repository to a /main subdirectory

$ mv /my-project /main 
$ mkdir /my-project 
$ mv /main /my-project 

Create a new working tree from an existing branch

$ cd /my-project/main 
$ git worktree add ../my-branch-working-dir my-branch 
$ cd ../my-branch-working-dir 

Create a new branch and new working tree

$ cd /my-project/main 
$ git worktree add -b my-new-branch ../my-new-branch-working-dir origin/master 
$ cd ../my-new-branch-working-dir 

Delete a working tree

$ cd /my-project 
$ rm -rf my-branch-working-dir 
$ git worktree prune 

fatal: my-branch is already checked out error

Deleting .git/worktrees/my-branch fixed the problem for me. See https://stackoverflow.com/q/33296185/101911

Reference / See also

https://github.com/blog/2042-git-2-5-including-multiple-worktrees-and-triangular-workflows

Using Firebase, Next.js, React, Redux, styled-components, and Reactstrap to build a Todo app

Here are some notes on making yet another task manager app using Firebase, Next.js, React, Redux, styled-components, Reactstrap (Bootstrap 4 alpha), Flow, and Prettier. I realize Hacker News clones are the new Todo app, but I built an old Todo app since I'm old. I starting out making a different app, but then I started putting my todo notes in my app so...

The app is here: https://kage.saltycrane.com and the code is here: https://github.com/saltycrane/kage.

Notes

  • Uses Firebase password authentication, Google authentication, and anonymous authentication when a user does not sign in.
  • Uses the Firebase database to persist tasks.
  • Next.js supports server-side rendering but I'm not taking advantage of this because I could not figure out how to configure Firebase auth on the server side.
  • I'm a fan of using Redux for applications. I think it makes complicated interactions easy to follow and debug. However, Redux is not good at handling complicated asynchronous side effects. I use redux-thunk a little to sequence asynchronous actions and async+await in some places. I tried to keep it's usage to a minimum because it is easy to misuse/overuse. I've played with redux-saga and it seems good. Maybe I will use it if I build something more complicated.
  • Uses my own redux-promise-memo library. It contains promise middleware copied from Gluestick, a reducer to store arguments for "memoization" and API status, and a memoize decorator which will prevent firing a promise-based action if has already been fired with the same arguments. "Memoization" is in quotes because it does not manage storing the cached data. It only manages whether to dispatch the action or not. Assume the user has stored the data in Redux.
  • Initially I didn't understand how to use styled-components. Later I realized styled-components could be used in place of sub objects in a styles object for a component when using e.g. Radium or Aphrodite. I like how it makes the JSX look clean. I've heard performance is a weakness of styled-components but they are working to improve it.
  • I'm a former backend developer so I don't know how to make things pretty. I guess I'll go with Bootstrap.
  • Flow has a lot of pain points but in-editor feedback is useful and it helps me write simpler code.
  • Prettier is great.

Other ideas to try


Here's how to try out Next.js on Mac OS X

Install Node.js and Yarn

  • Install Node.js 6.10 LTS using Homebrew
    $ brew install node@6 
    
    This is an alternate version of Node.js so the PATH environment variable needs to be updated to find it. Update ~/.bash_profile to add Node 6 to the beginning of the PATH variable
    $ echo 'export PATH="/usr/local/opt/node@6/bin:$PATH"' >> ~/.bash_profile 
    
    Restart the terminal to source ~/.bash_profile to update the PATH variable. After restarting the terminal, check that Node is installed:
    $ node --version
    v6.10.3
    
  • Install the Yarn package manager using the npm package manager that comes with Node.js
    $ npm install -g yarn 
    
    Verify yarn is installed:
    $ yarn --version
    0.24.5
    

Hello World with Next.js

  • Create a new project folder
    $ mkdir ~/myproject 
    $ cd ~/myproject 
    
  • Create a new file named package.json with the following:
    {
      "dependencies": {
        "next": "3.0.0-beta16",
        "react": "15.6.1",
        "react-dom": "15.6.1"
      },
      "scripts": {
        "dev": "next",
        "build": "next build",
        "start": "next start"
      }
    }
    
  • Install Next.js and React in the local project directory
    $ yarn 
    
  • Create a pages directory and a new file pages/index.js:
    import React from "react";
    
    export default class Hello extends React.Component {
      render() {
        return <div>Hello World</div>;
      }
    }
    
  • Start the dev server
    $ yarn dev 
    
  • Go to http://localhost:3000 in the browser

Flow type cheat sheet

Flow is a static type checker for Javascript. This is a list of Flow types generated from the source code in https://github.com/facebook/flow/tree/v0.52.0/ The script to generate this list is on github. Fixes welcome.

Note: I created a separate section for "private" or "magic" types with a $ in the name. See the note here and comment here. Update: Some these types are now documented here.

Flow version: v0.52.0

Core

Document Object Model (DOM)

Document Object Model (DOM) "private" types

Browser Object Model (BOM)

Browser Object Model (BOM) "private" types

CSS Object Model (CSSOM)

indexedDB

Node.js

Node.js "private" types

Comparing vanilla React, Redux, and Redux Thunk - Chained modals exercise part 3

This is a comparison of three methods for building a sequence of modals using: 1) React only 2) React+Redux and 3) React+Redux+Redux Thunk.

In part 1 and part 2, I created a configurable sequence of modals using React and React Router. Each modal had a form input. Clicking the "Next" button made an AJAX request and, on success, advanced to the next modal. The user was able to navigate between modals using the browser's history. This example adds better management of the form input state. Form input state can be pre-populated from the server and the input state is maintained when navigating forward or backward between modals.

React only

The first solution uses a parent component to manage the state of the chained modals. The full code is here and the demo is here.

App.js

RoutedApp is the top level component which contains the routing configuration. It is unchanged from part 2 except a formData prop is now passed in addition to the modalList prop to allow pre-filling form data from the server. Assume that "Servur" is data provided by the backend server.

This uses ES'15 arrow functions, argument destructuring and JSX spread. [App.js on github]

const RoutedApp = () => (
  <Router history={hashHistory}>
    <Route component={App}>
      <Route path="/" component={
        partial(ChainedModals, {
          modalList: ['/name', '/phone', '/done'],
          formData: {name: 'Servur'}
        })}>
        <Route path="/name" component={ModalName} />
        <Route path="/phone" component={ModalPhone} />
        <IndexRedirect to="/name" />
      </Route>
      <Route path="/done" />
    </Route>
  </Router>
);

const App = ({ children }) => (
  <div>
    <PageBehindModals />
    {children}
  </div>
);

const partial = (Comp, props) => (fprops) => <Comp {...props} {...fprops} />;
ChainedModals.js

ChainedModals manages two things: 1) form input state for all the modals and 2) advancing to the next modal in the sequence. To manage sequencing, the index of the current modal is stored in the component state. Using React's lifecycle methods componentWillMount and componentWillReceiveProps it determines the current index from the route before rendering. When a modal's "Next" button is clicked, it uses the current index to determine the next route and navigates to it.

ChainedModals also stores form data in its component state and defines methods for each modal to store their form data (_storeName and _storePhone). One problem with this approach is that specific modal functionality is defined in ChainedModals so it no longer works with an arbitrary list of modals passed in modalList as it did in parts 1 and 2.

This uses ES'15 nested destructuring and classes, and ES'17 class properties. [ChainedModals.js on github]

class ChainedModals extends Component {
  constructor(props) {
    super(props);
    const { formData } = props;
    this.state = {
      currIndex: null,
      formData: {
        name: null,
        phone: null,
        ...formData
      }
    };
  }

  render() {
    const { children } = this.props;
    const { currIndex, formData } = this.state;

    const modalElement = children && React.cloneElement(children, {
      step: currIndex + 1,
      formData: formData,
      storeName: this._storeName,
      storePhone: this._storePhone,
      gotoNext: this._gotoNext,
      backdrop: false,
      show: true
    });

    return (
      <div>
        <ModalBackdrop />
        {modalElement}
      </div>
    );
  }

  componentWillMount() {
    this._setIndexFromRoute(this.props);
  }

  componentWillReceiveProps(nextProps) {
    this._setIndexFromRoute(nextProps);
  }

  _setIndexFromRoute(props) {
    const { modalList, location: { pathname } } = props;
    const index = modalList.findIndex(path => path === pathname);
    this.setState({currIndex: index});
  }

  _gotoNext = () => {
    const { modalList } = this.props;
    const { currIndex } = this.state;
    const nextRoute = modalList[currIndex + 1];
    hashHistory.push(nextRoute);
  };

  _storeName = (name) => {
    this.setState({
      formData: {
        ...this.state.formData,
        name: name
      }
    });
  };

  _storePhone = (phone) => {
    this.setState({
      formData: {
        ...this.state.formData,
        phone: phone
      }
    });
  };
}
ModalName.js

ModalName changed to accept some form data from its parent and it also still manages the AJAX request and the state related to it.

This uses ES'15 destructuring, classes, and promises, and ES'17 rest/spread and class properties. [ModalName.js on github]

class ModalName extends Component {
  constructor(props) {
    super(props);
    const { formData: { name } } = props;
    this.state = {
      name: name || '',
      isRequesting: false,
      errorMsg: null
    };
  }

  render() {
    const { step, ...rest } = this.props;
    const { name, isRequesting, errorMsg } = this.state;

    return (
      <Modal {...rest}>
        <Modal.Header closeButton>
          <Modal.Title>Step {step} - Name</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          {isRequesting && <p><em>Making fake ajax request...</em></p>}
          {errorMsg && <p><em>{errorMsg}</em></p>}
          <Input
            label="Enter your name"
            type="text"
            bsSize="large"
            {...(errorMsg ? {bsStyle: 'error'} : {})}
            value={name}
            onChange={this._handleInputChange}
            ref={(c) => this._input = c}
          />
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="primary" onClick={this._handleClickNext}>Next</Button>
        </Modal.Footer>
      </Modal>
    );
  }

  _handleInputChange = () => {
    this.setState({name: this._input.getValue()});
  };

  _handleClickNext = () => {
    const { storeName, gotoNext } = this.props;
    const name = this._input.getValue();

    this.setState({isRequesting: true, errorMsg: null});
    request('/api/name', name)
      .then(() => {
        storeName(name);
        gotoNext();
      })
      .catch((error) => {
        this.setState({isRequesting: false, errorMsg: error});
      });
  };
}

Redux

Redux provides a way to manage application state outside of components. Examples of application state are the index of the current modal, the form input value, and status of the AJAX request. Using Redux to manage state makes React components more simple and reusable. Redux also makes it easier to manage complex interactions that affect multiple parts of the application.

Like React, data flows in one direction in Redux. Actions describe changes to be made, then reducers make changes to the state based on the actions, finally, the new state is passed to React components via props. Actions are simple objects. Reducers are pure functions that accept a state and action and return a new state. Because reducers are pure functions, they can be composed of many smaller reducers that each operate on a smaller slice of the state. See the Three Principles of Redux.

The code for the redux solution is here and the demo is here.

App.js

A Redux store is created to hold the application state. The state is initialized with modalList and formData that were previously passed into the ChainedModals component. The application element tree is wrapped with Provider which makes the Redux store available to it's child components.

I also added an event handler which dispatches a Redux action whenever the route changes. This idea was taken from this Redux issue and this related Redux pull request. Note: not all imports are shown in the snippet. See all the imports in [App.js on github]

import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { routeChanged } from '../actions';
import reducer from '../reducers';

const initialState = {
  modalList: [
    '/name',
    '/phone',
    '/done'
  ],
  currIndex: null,
  formData: {
    name: 'Servur',
    phone: null
  }
};

const store = createStore(reducer, initialState);

// Dispatch an action when the route changes.
hashHistory.listen(location => store.dispatch(routeChanged(location)));

const RoutedApp = () => (
  <Provider store={store}>
    <Router history={hashHistory}>
      <Route component={App}>
        <Route path="/" component={ChainedModals}>
          <Route path="/name" component={ModalName} />
          <Route path="/phone" component={ModalPhone} />
          <IndexRedirect to="/name" />
        </Route>
        <Route path="/done" />
      </Route>
    </Router>
  </Provider>
);

const App = ({ children }) => (
  <div>
    <PageBehindModals />
    {children}
  </div>
);
actions.js

I defined an action creator for when a route is changed and two for storing data from a user. The action creators return the action which is a simple object that has a type and some other simple data. The reducers will change the state based on the actions. [actions.js on github]

export const ROUTE_CHANGED = 'ROUTE_CHANGED';
export const STORE_NAME = 'STORE_NAME';
export const STORE_PHONE = 'STORE_PHONE';

export function routeChanged(location) {
  return {
    type: ROUTE_CHANGED,
    location: location
  }
}

export function storeName(name) {
  return {
    type: STORE_NAME,
    name: name
  };
}

export function storePhone(phone) {
  return {
    type: STORE_PHONE,
    phone: phone
  };
}
reducers.js

The _sequencing reducer sets the current index based on the route when the route changes. Previously this was done in the ChainedModals component. The _formData reducer stores data (either name or phone) from the user in the state. I wrap statements for each case in curly braces so that const and let declarations will have a more reasonable scope. Thanks Or! [reducers.js on github]

function modalsReducer(state, action) {
  return {
    ..._sequencing(state, action),
    formData: _formData(state.formData, action)
  }
}

function _sequencing(state, action) {
  switch (action.type) {
    case ROUTE_CHANGED: {
      const { location: { pathname } } = action;
      const index = state.modalList.findIndex(path => path === pathname);
      return {
        ...state,
        currIndex: index
      };
    }
    default:
      return state;
  }
}

function _formData(state, action) {
  switch (action.type) {
    case STORE_NAME: {
      return {
        ...state,
        name: action.name
      }
    }
    case STORE_PHONE: {
      return {
        ...state,
        phone: action.phone
      }
    }
    default:
      return state;
  }
}
ChainedModals.js

The ChainedModals component is now connected to Redux. Properties from the Redux state (currIndex, modalList, and formData) are passed into the React component as props with the same name. Similarly, the Redux actions storeName and storePhone are passed in as props with the same name. A lot of the code to manage the form state and sequencing is now removed. However it still defines a _gotoNext method which is used to navigate to the next route. Note in render(), I pull out the children and currIndex props and pass the rest of the props (e.g. formData, storeName, gotoNext) onto the child modal using the ES'17 object rest/spread operators. [ChainedModals.js on github]

class ChainedModals extends Component {
  render() {
    const { children, currIndex, ...rest } = this.props;

    const modalElement = children && React.cloneElement(children, {
      step: currIndex + 1,
      backdrop: false,
      show: true,
      gotoNext: this._gotoNext,
      ...rest
    });

    return (
      <div>
        <ModalBackdrop />
        {modalElement}
      </div>
    );
  }

  _gotoNext = () => {
    const { currIndex, modalList } = this.props;
    const nextRoute = modalList[currIndex + 1];
    hashHistory.push(nextRoute);
  };
}

export default connect(
  function mapStateToProps(state) {
    const { currIndex, modalList, formData } = state;
    return { currIndex, modalList, formData };
  },
  function mapDispatchToProps(dispatch) {
    return {
      storeName: (...args) => dispatch(storeName(...args)),
      storePhone: (...args) => dispatch(storePhone(...args))
    }
  }
)(ChainedModals);
ModalName.js

The individual modal components remain the same. They still make the ajax calls and manage the state related to that. [ModalName.js on github]

class ModalName extends Component {
  constructor(props) {
    super(props);
    const { formData: { name } } = props;
    this.state = {
      name: name || '',
      isRequesting: false,
      errorMsg: null
    };
  }

  render() {
    const { step, ...rest } = this.props;
    const { name, isRequesting, errorMsg } = this.state;

    return (
      <Modal {...rest}>
        <Modal.Header closeButton>
          <Modal.Title>Step {step} - Name</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          {isRequesting && <p><em>Making fake ajax request...</em></p>}
          <Input
            label="Enter your name"
            type="text"
            bsSize="large"
            {...(errorMsg ? {bsStyle: 'error'} : {})}
            help={errorMsg && <em>{errorMsg}</em>}
            value={name}
            onChange={this._handleInputChange}
            ref={(c) => this._input = c}
          />
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="primary" onClick={this._handleClickNext}>Next</Button>
        </Modal.Footer>
      </Modal>
    );
  }

  _handleInputChange = () => {
    this.setState({name: this._input.getValue()});
  };

  _handleClickNext = () => {
    const { storeName, gotoNext } = this.props;
    const name = this._input.getValue();

    this.setState({isRequesting: true, errorMsg: null});
    request('/api/name', name)
      .then(() => {
        storeName(name);
        gotoNext();
      })
      .catch((error) => {
        this.setState({isRequesting: false, errorMsg: error});
      });
  };
}

Redux Thunk

Using Redux added some boilerplate but made React components simpler by moving their state to the Redux store. However there is still some state and logic managed by the components. Redux Thunk is middleware for Redux that enables creating actions with side effects such as making asynchronous requests and changing the route. Redux Thunk legitimizes the pattern of providing dispatch to actions. See the Async Actions Redux documentation for detailed information about using Redux Thunk for asynchronous actions. Other alternatives for handling asynchronous actions are writing custom Redux middleware or using Redux Saga.

The code for the redux-thunk solution is here and the demo is here.

App.js

App.js is the same except I applied the redux thunk middleware. [App.js on github]

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(reducer, initialState, applyMiddleware(thunk));
actions.js

In actions.js I created three action creators with side effects: storeName and storePhone make asynchronous requests gotoNext changes the route. Note: though I used getState in my action creators, in general this is considered an anti-pattern. [actions.js on github]

export const ROUTE_CHANGED = 'ROUTE_CHANGED';
export const STORE_NAME_REQUESTED = 'STORE_NAME_REQUESTED';
export const STORE_NAME_SUCCEEDED = 'STORE_NAME_SUCCEEDED';
export const STORE_NAME_FAILED = 'STORE_NAME_FAILED';
export const STORE_PHONE_REQUESTED = 'STORE_PHONE_REQUESTED';
export const STORE_PHONE_SUCCEEDED = 'STORE_PHONE_SUCCEEDED';
export const STORE_PHONE_FAILED = 'STORE_PHONE_FAILED';

export function routeChanged(location) {
  return {
    type: ROUTE_CHANGED,
    location: location
  }
}

export function gotoNext() {
  return (dispatch, getState) => {
    const { currIndex, modalList } = getState();
    const nextRoute = modalList[currIndex + 1];
    hashHistory.push(nextRoute);
  }
}

export function storeName(name, onSuccess) {
  return dispatch => {
    dispatch(_storeNameRequested());
    return request('/api/name', name)
      .then(() => {
        dispatch(_storeNameSucceeded(name));
        onSuccess();
      })
      .catch(error => {
        dispatch(_storeNameFailed(error));
      });
  }
}

export function storePhone(phone, onSuccess) {
  return dispatch => {
    dispatch(_storePhoneRequested());
    return request('/api/phone', phone)
      .then(() => {
        dispatch(_storePhoneSucceeded(phone));
        onSuccess();
      })
      .catch(error => {
        dispatch(_storePhoneFailed(error));
      });
  }
}

function _storeNameRequested() {
  return {
    type: STORE_NAME_REQUESTED
  };
}

function _storeNameSucceeded(name) {
  return {
    type: STORE_NAME_SUCCEEDED,
    name: name
  };
}

function _storeNameFailed(errorMsg) {
  return {
    type: STORE_NAME_FAILED,
    errorMsg: errorMsg
  };
}

function _storePhoneRequested() {
  return {
    type: STORE_PHONE_REQUESTED
  };
}

function _storePhoneSucceeded(phone) {
  return {
    type: STORE_PHONE_SUCCEEDED,
    phone: phone
  };
}

function _storePhoneFailed(errorMsg) {
  return {
    type: STORE_PHONE_FAILED,
    errorMsg: errorMsg
  };
}
reducers.js

reducers.js now updates some state based on the status of the API request which is used by the modal components to show the spinner or validation errors. [reducers.js on github]

function modalsReducer(state, action) {
  return {
    ..._sequencing(state, action),
    formData: _formData(state.formData, action)
  }
}

function _sequencing(state, action) {
  switch (action.type) {
    case ROUTE_CHANGED: {
      const { location: { pathname } } = action;
      const index = state.modalList.findIndex(path => path === pathname);
      return {
        ...state,
        requestStatus: null,
        currIndex: index
      };
    }
    case STORE_NAME_REQUESTED:
    case STORE_PHONE_REQUESTED: {
      return {
        ...state,
        isRequesting: true,
        errorMsg: null
      }
    }
    case STORE_NAME_SUCCEEDED:
    case STORE_PHONE_SUCCEEDED: {
      return {
        ...state,
        isRequesting: false,
        errorMsg: null
      }
    }
    case STORE_NAME_FAILED:
    case STORE_PHONE_FAILED: {
      return {
        ...state,
        isRequesting: false,
        errorMsg: action.errorMsg
      }
    }
    default:
      return state;
  }
}

function _formData(state, action) {
  switch (action.type) {
    case STORE_NAME_SUCCEEDED: {
      return {
        ...state,
        name: action.name
      }
    }
    case STORE_PHONE_SUCCEEDED: {
      return {
        ...state,
        phone: action.phone
      }
    }
    default:
      return state;
  }
}
ChainedModals.js

ChainedModals is now a simpler functional component. It's purpose is connecting child modal components to redux and setting some default props for the modals. It is also used to display the backdrop behind the modals. [ChainedModals.js on github]

const ChainedModals = ({ children, ...rest }) => {
  const modalElement = children && React.cloneElement(children, rest);
  return (
    <div>
      <ModalBackdrop />
      {modalElement}
    </div>
  );
};

export default connect(
  function mapStateToProps(state) {
    const { currIndex, isRequesting, errorMsg, formData } = state;
    return {
      backdrop: false,
      show: true,
      step: currIndex + 1,
      isRequesting,
      errorMsg,
      formData
    };
  },
  function mapDispatchToProps(dispatch) {
    return {
      gotoNext: (...args) => dispatch(gotoNext(...args)),
      storeName: (...args) => dispatch(storeName(...args)),
      storePhone: (...args) => dispatch(storePhone(...args))
    }
  }
)(ChainedModals);
ModalName.js

Individual modal components now only use state for controlled inputs. [ModalName.js on github]

class ModalName extends Component {
  constructor(props) {
    super(props);
    const { formData: { name } } = props;
    this.state = {
      name: name || ''
    };
  }

  render() {
    const { step, isRequesting, errorMsg, ...rest } = this.props;
    const { name } = this.state;

    return (
      <Modal {...rest}>
        <Modal.Header closeButton>
          <Modal.Title>Step {step} - Name</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          {isRequesting && <p><em>Making fake ajax request...</em></p>}
          <Input
            label="Enter your name"
            type="text"
            bsSize="large"
            {...(errorMsg ? {bsStyle: 'error'} : {})}
            help={errorMsg && <em>{errorMsg}</em>}
            value={name}
            onChange={this._handleInputChange}
            ref={(c) => this._input = c}
          />
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="primary" onClick={this._handleClickNext}>Next</Button>
        </Modal.Footer>
      </Modal>
    );
  }

  _handleInputChange = () => {
    this.setState({name: this._input.getValue()});
  };

  _handleClickNext = () => {
    const { storeName, gotoNext } = this.props;
    const name = this._input.getValue();
    storeName(name, gotoNext);
  };
}

References / Further Reading (mostly from Redux author, Dan Abramov)

Some ES6+ features used in React development

The newest versions of JavaScript, ES2015 (ES6), ES2016 (ES7), ES2017 and beyond have many features that can be used today via Babel. Here are a few features I've used in React development.

Arrow functions (ES2015)

Arrow functions provide a shorthand syntax for defining functions. They also do not bind a this context so this from the lexical scope is used instead. If no curly braces are used, the value after the arrow is returned. There are other subtleties (e.g. hoisting and naming) associated with function expressions vs. function declarations. I like using arrow functions in blog posts for brevity, but I haven't decided if I like them in all circumstances yet. More information: Arrow Functions - YDKJS: ES6 & Beyond, Arrow functions - Exploring ES6, and Arrow This | getiblog for an explanation about this.

Here is a stateless React component defined using an arrow function:

const App = () => (
  <div>
    <PageBehindModals />
    <ChainedModals modalList={[ModalName, ModalPhone]} />
  </div>
);

Without arrow functions, it could be written as:

function App() {
  return (
    <div>
      <PageBehindModals />
      <ChainedModals modalList={[ModalName, ModalPhone]} />
    </div>
  );
}

Destructuring (ES2015)

The shorthand destructuring shown assigns properties of an object to variables of the same name. There is also a longhand syntax that allows you to assign to variables of different names. Destructuring works with nested objects, with arrays, and can be used in variable declarations, function return values and function arguments. More information: Destructuring - YDKJS: ES6 & Beyond

Here is an example destructuring the objects this.props and this.state:

class ChainedModals extends Component {
  render() {
    const { modalList } = this.props;
    const { currIndex, showModal } = this.state;
    // ..
  }
}

Without destructuring, it could be written as:

class ChainedModals extends Component {
  render() {
    const modalList = this.props.modalList;
    const currIndex = this.state.currIndex;
    const showModal = this.state.showModal;
    // ..
  }
}

Destructuring function arguments (ES2015)

Destructuring can be applied to function arguments that are objects or arrays. More information: Destructuring Parameters - YDKJS: ES6 & Beyond

This function expects a single object as an argument and it is destructured into onClickNext and step.

function ModalName({ onClickNext, step }) {
  return (
    <div>
      <h1>Step {step} - Name</h1>
      <Button onClick={onClickNext}>Next</Button>
    </div>
  );
}

Without destructuring, it could be written as:

function ModalName(props) {
  var onClickNext = props.onClickNext;
  var step = props.step;

  return (
    <div>
      <h1>Step {step} - Name</h1>
      <Button onClick={onClickNext}>Next</Button>
    </div>
  );
}

Nested destructuring (ES2015)

Destructuring also applies to objects nested in objects. More information: Nested Destructuring - YDKJS: ES6 & Beyond

Here is an example destructuring the nested props object:

function setIndexFromRoute(props) {
  const { modalList, location: { pathname } } = props;
  // ..
}

Without destructuring, it could be written as:

function setIndexFromRoute(props) {
  const modalList = props.modalList;
  const pathname = props.location.pathname;
  // ..
}

Object rest/spread operator (ES2018)

The ... rest operator gathers the rest of the items in the props object argument and puts them in the variable rest. The ... in the JSX is actually JSX syntax for spreading the props in the the rest object into individual props. More information: Object Rest/Spread Properties ECMAScript proposal, Objects Properties and ... - YDKJS: ES6 & Beyond, Using Object Spread Operator - Redux documentation, and JSX Spread Attributes - React documentation.

The object rest/spread operator is currently at stage 3 in the approval process so the earliest release would be ES2018. Note there is a rest/spread operator for arrays in ES2015.

function ModalName({ onClick, ...rest }) {
  return (
    <Modal {...rest}>
      <Button onClick={onClick}>Next</Button>
    </Modal>
  );
}

If only onClick, show, and backdrop props are passed to ModalName, it could be written like this. Using the rest and spread operators are useful when the properties are variable or unknown.

function ModalName(props) {
  var onClick = props.onClick;
  var show = props.show;
  var backdrop = props.backdrop;

  return (
    <Modal show={show} backdrop={backdrop}>
      <Button onClick={onClick}>Next</Button>
    </Modal>
  );
}
Object spread example

Here is an example using the object spread operator to merge 2 objects into a new object:

const initialState = {
  modalList: ["/name", "/phone", "/done"],
  currIndex: null,
  formData: {
    name: "Servur",
    phone: null,
  },
};

function handleRouteChange(state = initialState, pathname) {
  const index = state.modalList.findIndex(path => path === pathname);
  return { ...state, currIndex: index };
}

Here is how it could be done using Object.assign (ES2015):

function handleRouteChange(state = initialState, pathname) {
  const index = state.modalList.findIndex(path => path === pathname);
  return Object.assign({}, state, { currIndex: index });
}

Doing it using ES5 is much harder.

Concise properties (ES2015)

See Concise Properties - YDKJS: ES6 & Beyond

Here is an example using ES2105 concise properties:

const a = 1;
const b = 2;
const c = { a, b };

It could be written in ES5 without concise properties as:

var a = 1;
var b = 2;
var c = { a: a, b: b };

Array#includes (ES2016)

See Array#includes - YDKJS: ES6 & Beyond

Here is an example testing whether a value is included in an array using ES2016 Array#includes:

const selectedRows = [1, 2, 3];
const isSelected = selectedRows.includes(rowId);

Here is how it could be written using ES5:

var selectedRows = [1, 2, 3];
var isSelected = selectedRows.indexOf(rowId) >= 0;

See also

A chained modals example w/ React Router (part 2)

This is an example using React and React Router to create a sequence of chained modals. In part 1, I made a basic example where clicking a "Next" button advanced to the next modal in a list.

This example supports going back and forward using browser navigation and linking to a specific modal's URL. Additionally, each modal has a form input. On clicking the "Next" button, an AJAX request is made to save the data. On failure an error is displayed. On success the next modal is shown. During the request a spinner is shown. The full code is here and a demo is here.

App.js

RoutedApp is the top level component that configures the routes for the app. Each route has an associated component. Like the previous example, the modals are children of ChainedModals which is a child of App. However now each modal is explicitly declared in the element tree and React Router decides which modal to render based on the route. For example, navigating to the /name route renders ModalName as a child of ChainedModals as a child of App. In the App component, children is set to the ChainedModals element. In the ChainedModals component, children is set to either the ModalName or ModalPhone element depending on the route.

Instead of passing modal components to ChainedModals, a list of routes to the modals is passed in. The utility function partial allows me to create a component that is equivalent to ChainedModals with the modalList prop preset.

This uses ES'15 arrow functions, argument destructuring and JSX spread. [App.js on github]

const RoutedApp = () => (
  <Router history={hashHistory}>
    <Route component={App}>
      <Route path="/" component={
        partial(ChainedModals, {modalList: ['/name', '/phone', '/done']})}>
        <Route path="/name" component={ModalName} />
        <Route path="/phone" component={ModalPhone} />
        <IndexRedirect to="/name" />
      </Route>
      <Route path="/done" />
    </Route>
  </Router>
);

const App = ({ children }) => (
  <div>
    <PageBehindModals />
    {children}
  </div>
);

const partial = (Comp, props) => (fprops) => <Comp {...props} {...fprops} />;
ChainedModals.js

ChainedModals is a component that manages its child modal components. Unlike the previous example, the modal to be shown is determined by the current route. Before the component is rendered, the index of the current modal is determined by finding the current route in the modal list. This index is stored in the state. When the modal's "Next" button is clicked, _gotoNext determines the next route to be displayed and changes the route using hashHistory.push(). React.cloneElement is used to pass props to the child modal as shown in the React Router examples. This uses ES'15 nested destructuring and classes, and ES'17 class properties. [ChainedModals.js on github]

class ChainedModals extends Component {
  render() {
    const { children } = this.props;
    const { currIndex } = this.state;

    // Clone the child view element so we can pass props to it.
    const modalElement = children && React.cloneElement(children, {
      step: currIndex + 1,
      gotoNext: this._gotoNext,
      backdrop: false,
      show: true,
    });

    return (
      <div>
        <ModalBackdrop />
        {modalElement}
      </div>
    );
  }

  componentWillMount() {
    this._setIndexFromRoute(this.props);
  }

  componentWillReceiveProps(nextProps) {
    this._setIndexFromRoute(nextProps);
  }

  _setIndexFromRoute(props) {
    const { modalList, location: { pathname } } = props;
    const index = modalList.findIndex(path => path === pathname);
    this.setState({currIndex: index});
  }

  _gotoNext = () => {
    const { modalList } = this.props;
    const { currIndex } = this.state;
    const nextRoute = modalList[currIndex + 1];
    hashHistory.push(nextRoute);
  };
}
ModalName.js

ModalName is one of the modal components in the list. The _handleClickNext method makes a fake AJAX request using the request simulator function. request returns a Promise. On success, it calls gotoNext (which is passed in as a prop) to go to the next modal. The component state is used to show a spinner during the AJAX request (isRequesting) and to show validation errors (errorMsg). This uses ES'15 destructuring, classes, and promises, and ES'17 rest/spread and class properties. [ModalName.js on github]

class ModalName extends Component {
  state = {
    isRequesting: false,
    errorMsg: null
  };

  render() {
    const { step, ...props } = this.props;
    const { isRequesting, errorMsg } = this.state;

    return (
      <Modal {...props}>
        <Modal.Header closeButton>
          <Modal.Title>Step {step} - Name</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          {isRequesting && <p><em>Making fake ajax request...</em></p>}
          {errorMsg && <p><em>{errorMsg}</em></p>}
          <Input
            label="Enter your name"
            type="text"
            bsSize="large"
            {...(errorMsg ? {bsStyle: 'error'} : {})}
            ref={(c) => this._input = c}
          />
        </Modal.Body>
        <Modal.Footer>
          <Button bsStyle="primary" onClick={this._handleClickNext}>Next</Button>
        </Modal.Footer>
      </Modal>
    );
  }

  _handleClickNext = () => {
    const { gotoNext } = this.props;
    const name = this._input.getValue();

    this.setState({isRequesting: true, errorMsg: null});
    request('/api/name', name)
      .then(() => {
        gotoNext();
      })
      .catch((error) => {
        this.setState({isRequesting: false, errorMsg: error});
      });
  };
}

A chained modals example w/ React Bootstrap

This is an example using React to create a sequence of chained modals. Each modal has a "Next" button that, when clicked, advances to the next modal. The list of modals is configurable at page load. I used React-Bootstrap for the modals which provide animation when switching modals. The full code is here and a demo is here.

App.js

The top level app is a function that returns an element tree consisting of two elements: PageBehindModals and ChainedModals. ChainedModals is a parent of the modals which are passed in via the modalList prop. This uses ES'15 arrow functions. [App.js on github]

const App = () => (
  <div>
    <PageBehindModals />
    <ChainedModals modalList={[ModalName, ModalPhone]} />
  </div>
);
ModalName.js

ModalName is one of the modal components. It is built using React-Bootstrap's Modal and Button components. The step number and onClick handler are passed in as props from the parent component. The rest of the props (show and backdrop) are passed along to React-Bootstrap's Modal component. This uses ES'15 argument destructuring and ES'17 rest/spread. [ModalName.js on github]

const ModalName = ({ onClickNext, step, ...rest }) => (
  <Modal {...rest}>
    <Modal.Header closeButton>
      <Modal.Title>Step {step} - Name</Modal.Title>
    </Modal.Header>
    <Modal.Body>
      <p>Enter your name</p>
    </Modal.Body>
    <Modal.Footer>
      <Button bsStyle="primary" onClick={onClickNext}>Next</Button>
    </Modal.Footer>
  </Modal>
);
ChainedModals.js

ChainedModals keeps track of the current modal displayed in its component state. It defines _handleClickNext that is passed to the child modal components. When the child modal's "Next" button is clicked, this method is run. The method updates currIndex in the state, which causes a re-render. On re-render, ChainedModals finds a new modal component and renders it instead of the previous one. When the end of the modal list is reached, the modal is hidden, and the underlying page is shown. This uses ES'15 destructuring, ES'15 classes, and ES'17 class properties. [ChainedModals.js on github]

class ChainedModals extends Component {
  state = {
    currIndex: 0,
    showModal: true
  };

  render() {
    const { modalList } = this.props;
    const { currIndex, showModal } = this.state;
    const ModalComponent = modalList[currIndex];

    return (
      <div>
        {showModal && <ModalBackdrop />}
        <ModalComponent
          step={currIndex + 1}
          onClickNext={this._handleClickNext}
          backdrop={false}
          show={showModal}
        />
      </div>
    );
  }

  _handleClickNext = () => {
    const { modalList } = this.props;
    const { currIndex } = this.state;

    if (currIndex < modalList.length - 1) {
      this.setState({currIndex: currIndex + 1});
    } else {
      this.setState({showModal: false});
    }
  };
}
ModalBackdrop.js

A separate ModalBackdrop component is used so that the default modal's backdrop doesn't flash in and out when showing and hiding the modals. Since there is a separate modal backdrop, the backdrop prop is set to false for each modal. [ModalBackdrop.js on github]

const ModalBackdrop = () => <div className="modal-backdrop in"></div>;

How to set up Babel 6 with React on Mac OS X (command line only)

Babel is a tool used to compile JavaScript from ES6 to ES5. It is also the official way to compile React's JSX. Here is how to install and set up Babel 6 on OS X to run on the command line to compile React's JSX.

Directory structure

my-project
├── .babelrc
├── hello.babelized.js
├── hello.js
├── index.html
├── node_modules
└── package.json

Install Node.js

Installing Node.js provides npm, the Node package manager. npm is used to install Babel.

$ brew install node 
$ node --version
v5.5.0 

Create a package.json file

package.json is the configuration file for npm. The package.json file below specifies 3 packages to install. babel-cli is the Babel command line tool. babel-preset-es2015 and babel-preset-react are 2 packages that provide the plugins for Babel to transform ES6 and JSX respectively. The "scripts" section specifies a command to compile the hello.js file with Babel.

{
  "devDependencies": {
    "babel-cli": "6.5.1",
    "babel-preset-es2015": "6.5.0",
    "babel-preset-react": "6.5.0"
  },
  "scripts": {
    "build": "babel hello.js -o hello.babelized.js"
  }
}

Install Babel

npm install installs all the packages listed in package.json into a node_modules directory in the current directory. Packages can also be installed globally using the "-g" flag with npm install. Since this is a local install, the babel command is availiable as ./node_modules/.bin/babel.

$ npm install 
$ ./node_modules/.bin/babel --version 
6.5.1 (babel-core 6.5.1) 

Create a .babelrc file

The .babelrc file is used to configure Babel.

{
  "presets": ["react", "es2015"]
}

Create a index.html file

This uses the React libraries from Facebook's CCN and the compiled hello.babelized.js file. The React app lives in the "container" div.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Hello World</title>
  </head>
  <body>
    <div id="container"></div>
    <script src="https://fb.me/react-0.14.7.min.js"></script>
    <script src="https://fb.me/react-dom-0.14.7.min.js"></script>
    <script src="hello.babelized.js"></script>
  </body>
</html>

Create a hello.js file

Note: this hello.js does not use ES6.

var Hello = React.createClass({
  render: function() {
    return (
      <h1>Hello World</h1>
    );
  }
});

ReactDOM.render(
  <Hello />,
  document.getElementById('container')
);

Run Babel

$ npm run build 

This creates the hello.babelized.js file:

var Hello = React.createClass({
  displayName: 'Hello',

  render: function () {
    return React.createElement(
      'h1',
      null,
      'Hello World'
    );
  }
});

ReactDOM.render(React.createElement(Hello, null), document.getElementById('container'));

View in browser

$ open index.html 

See also