Update documents/index-editor to use Redux (#1151)

* Use redux

* Update tests
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index cff9a82..21515eb 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -22,6 +22,7 @@
 import revisionBrowserReducers from './rev-browser/reducers';
 import docEditorReducers from './doc-editor/reducers';
 import changesReducers from './changes/reducers';
+import indexEditorReducers from './index-editor/reducers';
 import "./assets/less/documents.less";
 
 FauxtonAPI.addReducers({
@@ -32,7 +33,8 @@
   partitionKey: partitionKeyReducers,
   docEditor: docEditorReducers,
   changes: changesReducers,
-  designDocInfo: designDocInfoReducers
+  designDocInfo: designDocInfoReducers,
+  indexEditor: indexEditorReducers
 });
 
 function getQueryParam (query) {
diff --git a/app/addons/documents/index-editor/__tests__/actions.test.js b/app/addons/documents/index-editor/__tests__/actions.test.js
index cba84f5..c01e469 100644
--- a/app/addons/documents/index-editor/__tests__/actions.test.js
+++ b/app/addons/documents/index-editor/__tests__/actions.test.js
@@ -10,22 +10,20 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import FauxtonAPI from "../../../../core/api";
-import Actions from "../actions";
-import Documents from "../../resources";
-import testUtils from "../../../../../test/mocha/testUtils";
-import sinon from "sinon";
-import "../../../documents/base";
-var assert = testUtils.assert;
-var restore = testUtils.restore;
+import FauxtonAPI from '../../../../core/api';
+import Actions from '../actions';
+import Documents from '../../resources';
+import testUtils from '../../../../../test/mocha/testUtils';
+import sinon from 'sinon';
+import '../../../documents/base';
 
+const restore = testUtils.restore;
 FauxtonAPI.router = new FauxtonAPI.Router([]);
 
-
 describe('Index Editor Actions', function () {
 
   describe('delete view', function () {
-    var designDocs, database, designDoc, designDocCollection, designDocId, viewName;
+    let designDocs, database, designDoc, designDocCollection, designDocId, viewName;
     beforeEach(function () {
       FauxtonAPI.reduxDispatch = sinon.stub();
       database = {
@@ -61,7 +59,7 @@
       designDoc.save = function () {
         return FauxtonAPI.Promise.resolve();
       };
-      var saveSpy = sinon.spy(designDoc, 'save');
+      const saveSpy = sinon.spy(designDoc, 'save');
       designDocs.fetch = function () {
         return FauxtonAPI.Promise.resolve();
       };
@@ -73,7 +71,7 @@
         designDoc: designDoc
       });
 
-      assert.ok(saveSpy.calledOnce);
+      sinon.assert.calledOnce(saveSpy);
     });
 
     it('deletes design doc if has no other views', function () {
@@ -82,7 +80,7 @@
       designDoc.destroy = function () {
         return FauxtonAPI.Promise.resolve();
       };
-      var destroySpy = sinon.spy(designDoc, 'destroy');
+      const destroySpy = sinon.spy(designDoc, 'destroy');
       designDocs.remove = function () {};
       designDocs.fetch = function () {
         return FauxtonAPI.Promise.resolve();
@@ -95,11 +93,11 @@
         designDoc: designDoc
       });
 
-      assert.ok(destroySpy.calledOnce);
+      sinon.assert.calledOnce(destroySpy);
     });
 
     it('navigates to all docs if was on view', function () {
-      var spy = sinon.spy(FauxtonAPI, 'navigate');
+      const spy = sinon.spy(FauxtonAPI, 'navigate');
 
       designDoc.save = function () {
         return FauxtonAPI.Promise.resolve();
@@ -114,8 +112,8 @@
         designDoc: designDoc,
         isOnIndex: true
       }).then(() => {
-        assert.ok(spy.getCall(0).args[0].match(/_all_docs/));
-        assert.ok(spy.calledOnce);
+        sinon.assert.calledWithMatch(spy, /_all_docs/);
+        sinon.assert.calledOnce(spy);
       });
     });
 
@@ -124,27 +122,27 @@
       const ddocModel = new Documents.Doc(ddoc, { database: database });
 
       ddocModel.setDdocView('testview', '() => {}', '() => {}');
-      assert.deepEqual(ddocModel.get('views'), {
+      expect(ddocModel.get('views')).toEqual({
         testview: {
           map: '() => {}',
           reduce: '() => {}'
         }
       });
-      assert.equal(ddocModel.get('language'), 'javascript');
+      expect(ddocModel.get('language')).toBe('javascript');
     });
 
     it('removes old view only when editting', function () {
       const viewInfo = {
-        newView: false,
+        isNewView: false,
         originalDesignDocName: 'test',
         designDocId: 'test',
         originalViewName: 'foo',
         viewName: 'bar'
       };
-      assert.isTrue(Actions.shouldRemoveDdocView(viewInfo));
+      expect(Actions.shouldRemoveDdocView(viewInfo)).toBe(true);
 
-      viewInfo.newView = true;
-      assert.isFalse(Actions.shouldRemoveDdocView(viewInfo));
+      viewInfo.isNewView = true;
+      expect(Actions.shouldRemoveDdocView(viewInfo)).toBe(false);
     });
   });
 });
diff --git a/app/addons/documents/index-editor/__tests__/viewIndex.components.test.js b/app/addons/documents/index-editor/__tests__/components.test.js
similarity index 62%
rename from app/addons/documents/index-editor/__tests__/viewIndex.components.test.js
rename to app/addons/documents/index-editor/__tests__/components.test.js
index 0c07d41..fde338c 100644
--- a/app/addons/documents/index-editor/__tests__/viewIndex.components.test.js
+++ b/app/addons/documents/index-editor/__tests__/components.test.js
@@ -9,81 +9,42 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
-import FauxtonAPI from "../../../../core/api";
-import Resources from "../../resources";
-import Views from "../components";
-import Actions from "../actions";
-import utils from "../../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
+
+import React from 'react';
 import {mount} from 'enzyme';
-import sinon from "sinon";
+import sinon from 'sinon';
+import FauxtonAPI from '../../../../core/api';
+import Views from '../components';
 import '../../base';
+
 FauxtonAPI.router = new FauxtonAPI.Router([]);
 
-const { assert } = utils;
-
-
-const resetStore = (designDocs) => {
-  Actions.editIndex({
-    database: { id: 'rockos-db' },
-    newView: false,
-    viewName: 'test-view',
-    designDocs: getDesignDocsCollection(designDocs),
-    designDocId: designDocs[0]._id
-  });
-};
-
-const getDesignDocsCollection = (designDocs) => {
-  designDocs = designDocs.map(function (doc) {
-    return Resources.Doc.prototype.parse(doc);
-  });
-
-  return new Resources.AllDocs(designDocs, {
-    params: { limit: 10 },
-    database: {
-      safeID: () => { return 'id'; }
-    }
-  });
-};
-
-
-describe('reduce editor', () => {
-  let reduceEl;
-
+describe('ReduceEditor', () => {
   describe('getReduceValue', () => {
+    const defaultProps = {
+      reduceOptions: [],
+      hasReduce: false,
+      hasCustomReduce: false,
+      reduce: null,
+      reduceSelectedOption: 'NONE',
+      updateReduceCode: () => {},
+      selectReduceChanged: () => {}
+    };
 
     it('returns null for none', () => {
-      const designDoc = {
-        _id: '_design/test-doc',
-        views: {
-          'test-view': {
-            map: '() => {};'
-          }
-        }
-      };
-
-      resetStore([designDoc]);
-
-      reduceEl = mount(<Views.ReduceEditor/>);
-      assert.ok(_.isNull(reduceEl.instance().getReduceValue()));
+      const reduceEl = mount(<Views.ReduceEditor
+        {...defaultProps}
+      />);
+      expect(reduceEl.instance().getReduceValue()).toBeNull();
     });
 
     it('returns built in for built in reduce', () => {
-      const designDoc = {
-        _id: '_design/test-doc',
-        views: {
-          'test-view': {
-            map: '() => {};',
-            reduce: '_sum'
-          }
-        }
-      };
-
-      resetStore([designDoc]);
-
-      reduceEl = mount(<Views.ReduceEditor/>);
-      assert.equal(reduceEl.instance().getReduceValue(), '_sum');
+      const reduceEl = mount(<Views.ReduceEditor
+        {...defaultProps}
+        reduce='_sum'
+        hasReduce={true}
+      />);
+      expect(reduceEl.instance().getReduceValue()).toBe('_sum');
     });
 
   });
@@ -107,7 +68,7 @@
         value: '_design/test-doc'
       }
     });
-    assert.ok(spy.calledOnce);
+    sinon.assert.calledOnce(spy);
   });
 
   it('shows new design doc field when set to new-doc', () => {
@@ -119,7 +80,7 @@
         onChangeNewDesignDocName={() => {}}
       />);
 
-    assert.equal(selectorEl.find('#new-ddoc-section').length, 1);
+    expect(selectorEl.find('#new-ddoc-section').length).toBe(1);
   });
 
   it('hides new design doc field when design doc selected', () => {
@@ -131,7 +92,7 @@
         onChangeNewDesignDocName={() => {}}
       />);
 
-    assert.equal(selectorEl.find('#new-ddoc-section').length, 0);
+    expect(selectorEl.find('#new-ddoc-section').length).toBe(0);
   });
 
   it('always passes validation when design doc selected', () => {
@@ -143,7 +104,7 @@
         onChangeNewDesignDocName={() => {}}
       />);
 
-    assert.equal(selectorEl.instance().validate(), true);
+    expect(selectorEl.instance().validate()).toBe(true);
   });
 
   it('fails validation if new doc name entered/not entered', () => {
@@ -157,7 +118,7 @@
       />);
 
     // it shouldn't validate at this point: no new design doc name has been entered
-    assert.equal(selectorEl.instance().validate(), false);
+    expect(selectorEl.instance().validate()).toBe(false);
   });
 
   it('passes validation if new doc name entered/not entered', () => {
@@ -169,7 +130,7 @@
         onSelectDesignDoc={() => { }}
         onChangeNewDesignDocName={() => {}}
       />);
-    assert.equal(selectorEl.instance().validate(), true);
+    expect(selectorEl.instance().validate()).toBe(true);
   });
 
 
@@ -181,7 +142,7 @@
         onSelectDesignDoc={() => { }}
         onChangeNewDesignDocName={() => {}}
       />);
-    assert.equal(selectorEl.find('.help-link').length, 0);
+    expect(selectorEl.find('.help-link').length).toBe(0);
   });
 
   it('includes help doc link when supplied', () => {
@@ -194,27 +155,51 @@
         docLink={docLink}
         onChangeNewDesignDocName={() => {}}
       />);
-    assert.equal(selectorEl.find('.help-link').length, 1);
-    assert.equal(selectorEl.find('.help-link').prop('href'), docLink);
+    expect(selectorEl.find('.help-link').length).toBe(1);
+    expect(selectorEl.find('.help-link').prop('href')).toBe(docLink);
   });
 });
 
-
-describe('Editor', () => {
-  let editorEl;
-
-  beforeEach(() => {
-    editorEl = mount(<Views.EditorController />);
-  });
+describe('IndexEditor', () => {
+  const defaultProps = {
+    isLoading: false,
+    isNewView: false,
+    isNewDesignDoc: false,
+    viewName: '',
+    database: {},
+    originalViewName: '',
+    originalDesignDocName: '',
+    designDoc: {},
+    designDocId: '',
+    designDocList: [],
+    map: '',
+    reduce: '',
+    designDocs: {},
+    updateNewDesignDocName: () => {},
+    updateMapCode: () => {},
+    selectDesignDoc: () => {},
+    onChangeNewDesignDocName: () => {},
+    reduceOptions: [],
+    reduceSelectedOption: 'NONE',
+    hasReduce: false,
+    hasCustomReduce: false,
+    updateReduceCode: () => {},
+    selectReduceChanged: () => {}
+  };
 
   it('calls changeViewName on view name change', () => {
-    const viewName = 'new-name';
-    const spy = sinon.spy(Actions, 'changeViewName');
+    const spy = sinon.spy();
+    const editorEl = mount(<Views.IndexEditor
+      {...defaultProps}
+      viewName='new-name'
+      changeViewName={spy}
+    />);
+
     editorEl.find('#index-name').simulate('change', {
       target: {
-        value: viewName
+        value: 'newViewName'
       }
     });
-    assert.ok(spy.calledWith(viewName));
+    sinon.assert.calledWith(spy, 'newViewName');
   });
 });
diff --git a/app/addons/documents/index-editor/__tests__/reducers.test.js b/app/addons/documents/index-editor/__tests__/reducers.test.js
new file mode 100644
index 0000000..ee60b4e
--- /dev/null
+++ b/app/addons/documents/index-editor/__tests__/reducers.test.js
@@ -0,0 +1,299 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import Documents from '../../../documents/resources';
+import reducer, { hasCustomReduce, getDesignDocIds } from '../reducers';
+import ActionTypes from '../actiontypes';
+import '../../base';
+
+describe('IndexEditor Reducer', () => {
+  describe('map editor', () => {
+    it('new view is assigned the default map code', () => {
+      const action = {
+        type: ActionTypes.EDIT_NEW_INDEX,
+        options: {
+          isNewView: true,
+          //designDocs: {find: () => { return { dDocModel: () => {}}; }}
+        }
+      };
+      const newState = reducer(undefined, action);
+      expect(newState.view.map).toBe('function (doc) {\n  emit(doc._id, 1);\n}');
+    });
+  });
+
+  describe('reduce editor', () => {
+    describe('hasCustomReduce', () => {
+      it('is false for no reduce', () => {
+        const designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: '() => {};'
+            }
+          }
+        };
+        const designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: () => { return 'id';}
+          }
+        });
+        const action = {
+          type: ActionTypes.EDIT_NEW_INDEX,
+          options: {
+            isNewView: false,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        };
+        const newState = reducer(undefined, action);
+        expect(hasCustomReduce(newState)).toBe(false);
+      });
+
+      it('is false for built in reduce', () => {
+        const designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: '() => {};',
+              reduce: '_sum'
+            }
+          }
+        };
+        const designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: () => { return 'id';}
+          }
+        });
+        const action = {
+          type: ActionTypes.EDIT_NEW_INDEX,
+          options: {
+            isNewView: false,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        };
+        const newState = reducer(undefined, action);
+        expect(hasCustomReduce(newState)).toBe(false);
+      });
+
+      it('is true for custom reduce', () => {
+        const designDoc = {
+          _id: '_design/test-doc',
+          views: {
+            'test-view': {
+              map: '() => {};',
+              reduce: 'function (reduce) { reduce(); }'
+            }
+          }
+        };
+        const designDocs = new Documents.AllDocs([designDoc], {
+          params: { limit: 10 },
+          database: {
+            safeID: () => { return 'id';}
+          }
+        });
+        const action = {
+          type: ActionTypes.EDIT_NEW_INDEX,
+          options: {
+            isNewView: false,
+            viewName: 'test-view',
+            designDocs: designDocs,
+            designDocId: designDoc._id
+          }
+        };
+
+        const newState = reducer(undefined, action);
+        expect(hasCustomReduce(newState)).toBe(true);
+      });
+    });
+
+    describe('SELECT_REDUCE_CHANGE', () => {
+      const designDoc = {
+        _id: '_design/test-doc',
+        views: {
+          'test-view': {
+            map: '() => {};'
+          }
+        }
+      };
+      const designDocs = new Documents.AllDocs([designDoc], {
+        params: { limit: 10 },
+        database: {
+          safeID: () => { return 'id';}
+        }
+      });
+      const editAction = {
+        type: ActionTypes.EDIT_NEW_INDEX,
+        options: {
+          isNewView: false,
+          viewName: 'test-view',
+          designDocs: designDocs,
+          designDocId: designDoc._id
+        }
+      };
+
+      it('NONE returns null reduce', () => {
+        let newState = reducer(undefined, editAction);
+        const selectReduceaction = {
+          type: ActionTypes.SELECT_REDUCE_CHANGE,
+          reduceSelectedOption: 'NONE'
+        };
+        newState = reducer(newState, selectReduceaction);
+        expect(newState.view.reduce).toBe('');
+      });
+
+      it('builtin returns builtin reduce', () => {
+        let newState = reducer(undefined, editAction);
+        const selectReduceAction = {
+          type: ActionTypes.SELECT_REDUCE_CHANGE,
+          reduceSelectedOption: '_sum'
+        };
+        newState = reducer(newState, selectReduceAction);
+        expect(newState.view.reduce).toBe('_sum');
+      });
+
+      it('custom returns custom reduce', () => {
+        let newState = reducer(undefined, editAction);
+        const selectReduceAction = {
+          type: ActionTypes.SELECT_REDUCE_CHANGE,
+          reduceSelectedOption: 'CUSTOM'
+        };
+        newState = reducer(newState, selectReduceAction);
+        expect(newState.view.reduce).toBe('function (keys, values, rereduce) {\n  if (rereduce) {\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}');
+      });
+    });
+  });
+
+  describe('design doc selector', () => {
+    const designDoc = {
+      _id: '_design/test-doc',
+      views: {
+        'test-view': {
+          map: 'boom'
+        }
+      }
+    };
+
+    const mangoDoc = {
+      "_id": "_design/123mango",
+      "id": "_design/123mango",
+      "key": "_design/123mango",
+      "value": {
+        "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
+      },
+      "doc": {
+        "_id": "_design/123mango",
+        "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
+        "views": {
+          "test-view": {
+            "map": "function(doc) {\n  emit(doc._id, 2);\n}"
+          },
+          "new-view": {
+            "map": "function(doc) {\n  if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n    emit(doc._id, 1);\n}",
+            "reduce": "_sum"
+          }
+        },
+        "language": "query",
+        "indexes": {
+          "newSearch": {
+            "analyzer": "standard",
+            "index": "function(doc){\n index(\"default\", doc._id);\n}"
+          }
+        }
+      }
+    };
+
+    const designDocArray = _.map([designDoc, mangoDoc], (doc) => {
+      return Documents.Doc.prototype.parse(doc);
+    });
+
+    const designDocs = new Documents.AllDocs(designDocArray, {
+      params: { limit: 10 },
+      database: {
+        safeID: () => { return 'id'; }
+      }
+    });
+
+    const editAction = {
+      type: ActionTypes.EDIT_INDEX,
+      options: {
+        isNewView: false,
+        viewName: 'test-view',
+        designDocs: designDocs,
+        designDocId: designDoc._id
+      }
+    };
+
+    it('DESIGN_DOC_CHANGE changes design doc id', () => {
+      let newState = reducer(undefined, editAction);
+      const designDocId = 'another-one';
+      const ddocChangeAction = {
+        type: ActionTypes.DESIGN_DOC_CHANGE,
+        options: {
+          value: designDocId
+        }
+      };
+      newState = reducer(newState, ddocChangeAction);
+      expect(newState.designDocId).toBe(designDocId);
+    });
+
+    it('only filters mango docs', () => {
+      const newState = reducer(undefined, editAction);
+      const designDocs = getDesignDocIds(newState);
+
+      expect(designDocs.length).toBe(1);
+      expect(designDocs[0]).toBe('_design/test-doc');
+    });
+  });
+
+  describe('EDIT_INDEX', () => {
+    const designDoc = {
+      _id: '_design/test-doc',
+      views: {
+        'test-view': {
+          map: 'boom'
+        }
+      }
+    };
+    const designDocs = new Documents.AllDocs([designDoc], {
+      params: { limit: 10 },
+      database: {
+        safeID: () => { return 'id';}
+      }
+    });
+
+    it('can set reduce for new design doc', () => {
+      const editAction = {
+        type: ActionTypes.EDIT_INDEX,
+        options: {
+          isNewView: true,
+          isNewDesignDoc: true,
+          viewName: 'test-view',
+          designDocs: designDocs,
+          designDocId: undefined
+        }
+      };
+      let newState = reducer(undefined, editAction);
+
+      const selectReduceAction = {
+        type: ActionTypes.SELECT_REDUCE_CHANGE,
+        reduceSelectedOption: '_sum'
+      };
+      newState = reducer(newState, selectReduceAction);
+      expect(newState.view.reduce).toBe('_sum');
+    });
+  });
+});
diff --git a/app/addons/documents/index-editor/__tests__/stores.test.js b/app/addons/documents/index-editor/__tests__/stores.test.js
deleted file mode 100644
index 2e1e6e7..0000000
--- a/app/addons/documents/index-editor/__tests__/stores.test.js
+++ /dev/null
@@ -1,337 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../../core/api";
-import Stores from "../stores";
-import ActionTypes from "../actiontypes";
-import Documents from "../../../documents/resources";
-import testUtils from "../../../../../test/mocha/testUtils";
-import '../../base';
-const assert = testUtils.assert;
-let store;
-let dispatchToken;
-
-describe('IndexEditorStore', () => {
-
-  beforeEach(() => {
-    store = new Stores.IndexEditorStore();
-    dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch.bind(store));
-  });
-
-  afterEach(() => {
-    FauxtonAPI.dispatcher.unregister(dispatchToken);
-  });
-
-  describe('map editor', () => {
-
-    describe('new view', () => {
-
-      beforeEach(() => {
-        FauxtonAPI.dispatch({
-          type: ActionTypes.EDIT_NEW_INDEX,
-          options: {
-            newView: true
-          }
-        });
-      });
-
-      it('returns default map', () => {
-        assert.equal(store.getMap(), 'function (doc) {\n  emit(doc._id, 1);\n}');
-      });
-    });
-
-  });
-
-  describe('reduce editor', () => {
-    describe('has custom reduce', () => {
-      it('is false for no reduce', () => {
-        const designDoc = {
-          _id: '_design/test-doc',
-          views: {
-            'test-view': {
-              map: '() => {};'
-            }
-          }
-        };
-
-        const designDocs = new Documents.AllDocs([designDoc], {
-          params: { limit: 10 },
-          database: {
-            safeID: () => { return 'id';}
-          }
-        });
-
-        FauxtonAPI.dispatch({
-          type: ActionTypes.EDIT_NEW_INDEX,
-          options: {
-            newView: false,
-            viewName: 'test-view',
-            designDocs: designDocs,
-            designDocId: designDoc._id
-          }
-        });
-
-        assert.notOk(store.hasCustomReduce());
-      });
-
-      it('is false for built in reduce', () => {
-        const designDoc = {
-          _id: '_design/test-doc',
-          views: {
-            'test-view': {
-              map: '() => {};',
-              reduce: '_sum'
-            }
-          }
-        };
-
-        const designDocs = new Documents.AllDocs([designDoc], {
-          params: { limit: 10 },
-          database: {
-            safeID: () => { return 'id';}
-          }
-        });
-        FauxtonAPI.dispatch({
-          type: ActionTypes.EDIT_NEW_INDEX,
-          options: {
-            newView: false,
-            viewName: 'test-view',
-            designDocs: designDocs,
-            designDocId: designDoc._id
-          }
-        });
-
-        assert.notOk(store.hasCustomReduce());
-      });
-
-      it('is true for custom reduce', () => {
-        const designDoc = {
-          _id: '_design/test-doc',
-          views: {
-            'test-view': {
-              map: '() => {};',
-              reduce: 'function (reduce) { reduce(); }'
-            }
-          }
-        };
-
-        const designDocs = new Documents.AllDocs([designDoc], {
-          params: { limit: 10 },
-          database: {
-            safeID: () => { return 'id';}
-          }
-        });
-
-        FauxtonAPI.dispatch({
-          type: ActionTypes.EDIT_NEW_INDEX,
-          options: {
-            newView: false,
-            viewName: 'test-view',
-            designDocs: designDocs,
-            designDocId: designDoc._id
-          }
-        });
-
-        assert.ok(store.hasCustomReduce());
-      });
-
-    });
-
-    //show default reduce
-    describe('SELECT_REDUCE_CHANGE', () => {
-
-      beforeEach(() => {
-        const designDoc = {
-          _id: '_design/test-doc',
-          views: {
-            'test-view': {
-              map: '() => {};'
-            }
-          }
-        };
-
-        const designDocs = new Documents.AllDocs([designDoc], {
-          params: { limit: 10 },
-          database: {
-            safeID: () => { return 'id';}
-          }
-        });
-
-        FauxtonAPI.dispatch({
-          type: ActionTypes.EDIT_NEW_INDEX,
-          options: {
-            newView: false,
-            viewName: 'test-view',
-            designDocs: designDocs,
-            designDocId: designDoc._id
-          }
-        });
-      });
-
-      it('NONE returns null reduce', () => {
-        FauxtonAPI.dispatch({
-          type: ActionTypes.SELECT_REDUCE_CHANGE,
-          reduceSelectedOption: 'NONE'
-        });
-        assert.ok(_.isNull(store.getReduce()));
-      });
-
-      it('builtin returns bultin reduce', () => {
-        FauxtonAPI.dispatch({
-          type: ActionTypes.SELECT_REDUCE_CHANGE,
-          reduceSelectedOption: '_sum'
-        });
-        assert.equal(store.getReduce(), '_sum');
-      });
-
-      it('custom returns custom reduce', () => {
-        FauxtonAPI.dispatch({
-          type: ActionTypes.SELECT_REDUCE_CHANGE,
-          reduceSelectedOption: 'CUSTOM'
-        });
-        assert.equal(store.getReduce(), 'function (keys, values, rereduce) {\n  if (rereduce) {\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}');
-      });
-    });
-  });
-
-
-  describe('design doc selector', () => {
-    let designDoc;
-
-    beforeEach(() => {
-      designDoc = {
-        _id: '_design/test-doc',
-        views: {
-          'test-view': {
-            map: 'boom'
-          }
-        }
-      };
-
-      const mangoDoc = {
-        "_id": "_design/123mango",
-        "id": "_design/123mango",
-        "key": "_design/123mango",
-        "value": {
-          "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
-        },
-        "doc": {
-          "_id": "_design/123mango",
-          "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
-          "views": {
-            "test-view": {
-              "map": "function(doc) {\n  emit(doc._id, 2);\n}"
-            },
-            "new-view": {
-              "map": "function(doc) {\n  if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n    emit(doc._id, 1);\n}",
-              "reduce": "_sum"
-            }
-          },
-          "language": "query",
-          "indexes": {
-            "newSearch": {
-              "analyzer": "standard",
-              "index": "function(doc){\n index(\"default\", doc._id);\n}"
-            }
-          }
-        }
-      };
-
-      const designDocArray = _.map([designDoc, mangoDoc], (doc) => {
-        return Documents.Doc.prototype.parse(doc);
-      });
-
-      var designDocs = new Documents.AllDocs(designDocArray, {
-        params: { limit: 10 },
-        database: {
-          safeID: () => { return 'id'; }
-        }
-      });
-
-      FauxtonAPI.dispatch({
-        type: ActionTypes.EDIT_INDEX,
-        options: {
-          newView: false,
-          viewName: 'test-view',
-          designDocs: designDocs,
-          designDocId: designDoc._id
-        }
-      });
-    });
-
-    afterEach(() => {
-      store.reset();
-    });
-
-    it('DESIGN_DOC_CHANGE changes design doc id', () => {
-      const designDocId = 'another-one';
-      FauxtonAPI.dispatch({
-        type: ActionTypes.DESIGN_DOC_CHANGE,
-        options: {
-          value: designDocId
-        }
-      });
-      assert.equal(store.getDesignDocId(), designDocId);
-    });
-
-    it('only filters mango docs', () => {
-      const designDocs = store.getAvailableDesignDocs();
-      assert.equal(designDocs.length, 1);
-      assert.equal(designDocs[0], '_design/test-doc');
-    });
-  });
-
-  describe('EDIT_INDEX', () => {
-    let designDoc, designDocs;
-
-    beforeEach(() => {
-      designDoc = {
-        _id: '_design/test-doc',
-        views: {
-          'test-view': {
-            map: 'boom'
-          }
-        }
-      };
-
-      designDocs = new Documents.AllDocs([designDoc], {
-        params: { limit: 10 },
-        database: {
-          safeID: () => { return 'id';}
-        }
-      });
-
-    });
-
-    it('can set reduce for new design doc', () => {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.EDIT_INDEX,
-        options: {
-          newView: true,
-          newDesignDoc: true,
-          viewName: 'test-view',
-          designDocs: designDocs,
-          designDocId: undefined
-        }
-      });
-
-      FauxtonAPI.dispatch({
-        type: ActionTypes.SELECT_REDUCE_CHANGE,
-        reduceSelectedOption: '_sum'
-      });
-
-      assert.equal(store.getReduce(), '_sum');
-    });
-
-  });
-
-});
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index c25fa99..ad261e0 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -10,40 +10,40 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import Documents from "../resources";
-import ActionTypes from "./actiontypes";
-import SidebarActions from "../sidebar/actions";
+import app from '../../../app';
+import FauxtonAPI from '../../../core/api';
+import Documents from '../resources';
+import ActionTypes from './actiontypes';
+import SidebarActions from '../sidebar/actions';
 
-function selectReduceChanged (reduceOption) {
-  FauxtonAPI.dispatch({
+const selectReduceChanged = (reduceOption) => (dispatch) => {
+  dispatch({
     type: ActionTypes.SELECT_REDUCE_CHANGE,
     reduceSelectedOption: reduceOption
   });
-}
+};
 
-function changeViewName (name) {
-  FauxtonAPI.dispatch({
+const changeViewName = (name) => (dispatch) => {
+  dispatch({
     type: ActionTypes.VIEW_NAME_CHANGE,
     name: name
   });
-}
+};
 
-function editIndex (options) {
-  FauxtonAPI.dispatch({
+const editIndex = (options) => (dispatch) => {
+  dispatch({
     type: ActionTypes.EDIT_INDEX,
     options: options
   });
-}
+};
 
-function clearIndex () {
-  FauxtonAPI.dispatch({ type: ActionTypes.CLEAR_INDEX });
-}
+const dispatchClearIndex = () => {
+  FauxtonAPI.reduxDispatch({ type: ActionTypes.CLEAR_INDEX });
+};
 
-function fetchDesignDocsBeforeEdit (options) {
+const dispatchFetchDesignDocsBeforeEdit = (options) => {
   options.designDocs.fetch({reset: true}).then(() => {
-    this.editIndex(options);
+    editIndex(options)(FauxtonAPI.reduxDispatch);
   }, xhr => {
     let errorMsg = 'Error';
     if (xhr.responseJSON && xhr.responseJSON.error === 'not_found') {
@@ -57,16 +57,16 @@
       clear:  true
     });
   });
-}
+};
 
-function shouldRemoveDdocView(viewInfo) {
-  return !viewInfo.newView &&
+const shouldRemoveDdocView = (viewInfo) => {
+  return !viewInfo.isNewView &&
           viewInfo.originalDesignDocName === viewInfo.designDocId &&
           viewInfo.originalViewName !== viewInfo.viewName;
-}
+};
 
-function saveView (viewInfo) {
-  var designDoc = viewInfo.designDoc;
+const saveView = (viewInfo) => (dispatch) => {
+  const designDoc = viewInfo.designDoc;
   designDoc.setDdocView(viewInfo.viewName, viewInfo.map, viewInfo.reduce);
 
   FauxtonAPI.addNotification({
@@ -89,8 +89,8 @@
 
     // if the user just saved an existing view to a different design doc, remove the view
     // from the old design doc and delete if it's empty
-    if (!viewInfo.newView && viewInfo.originalDesignDocName !== viewInfo.designDocId) {
-      var oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
+    if (!viewInfo.isNewView && viewInfo.originalDesignDocName !== viewInfo.designDocId) {
+      const oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
       safeDeleteIndex(oldDesignDoc, viewInfo.designDocs, 'views', viewInfo.originalViewName, {
         onSuccess: function () {
           SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
@@ -102,8 +102,8 @@
       addDesignDoc(designDoc);
     }
     SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
-    FauxtonAPI.dispatch({ type: ActionTypes.VIEW_SAVED });
-    var fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
+    dispatch({ type: ActionTypes.VIEW_SAVED });
+    const fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
     FauxtonAPI.navigate(fragment, { trigger: true });
   }, (xhr) => {
     FauxtonAPI.addNotification({
@@ -112,16 +112,16 @@
       clear: true
     });
   });
-}
+};
 
-function addDesignDoc (designDoc) {
-  FauxtonAPI.dispatch({
+const addDesignDoc = (designDoc) => (dispatch) => {
+  dispatch({
     type: ActionTypes.VIEW_ADD_DESIGN_DOC,
     designDoc: designDoc.toJSON()
   });
-}
+};
 
-function deleteView (options) {
+const deleteView = (options) => {
 
   function onSuccess () {
 
@@ -143,11 +143,11 @@
   }
 
   return safeDeleteIndex(options.designDoc, options.designDocs, 'views', options.indexName, { onSuccess: onSuccess });
-}
+};
 
-function cloneView (params) {
-  var targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
-  var indexes = targetDesignDoc.get('views');
+const cloneView = (params) => {
+  const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
+  let indexes = targetDesignDoc.get('views');
   if (indexes && _.has(indexes, params.newIndexName)) {
     FauxtonAPI.addNotification({
       msg: 'That index name is already used in this design doc. Please enter a new name.',
@@ -159,14 +159,14 @@
   if (!indexes) {
     indexes = {};
   }
-  var sourceDesignDoc = findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
-  var sourceDesignDocJSON = sourceDesignDoc.toJSON();
+  const sourceDesignDoc = findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
+  const sourceDesignDocJSON = sourceDesignDoc.toJSON();
 
   // this sets whatever content is in the source index into the target design doc under the new index name
   indexes[params.newIndexName] = sourceDesignDocJSON.views[params.sourceIndexName];
   targetDesignDoc.set({ views: indexes });
 
-  targetDesignDoc.save().then(function () {
+  targetDesignDoc.save().then(() => {
     params.onComplete();
     FauxtonAPI.addNotification({
       msg: 'The index has been cloned.',
@@ -174,63 +174,62 @@
       clear: true
     });
     SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
-  },
-  function (xhr) {
+  }, (xhr) => {
     params.onComplete();
-    var responseText = JSON.parse(xhr.responseText).reason;
+    const responseText = JSON.parse(xhr.responseText).reason;
     FauxtonAPI.addNotification({
       msg: 'Clone failed: ' + responseText,
       type: 'error',
       clear: true
     });
   });
-}
+};
 
-function gotoEditViewPage (databaseName, partitionKey, designDocName, indexName) {
+const gotoEditViewPage = (databaseName, partitionKey, designDocName, indexName) => {
   FauxtonAPI.navigate('#' + FauxtonAPI.urls('view', 'edit', encodeURIComponent(databaseName),
     (partitionKey ? encodeURIComponent(partitionKey) : ''),
     encodeURIComponent(designDocName), encodeURIComponent(indexName)));
-}
+};
 
-function updateMapCode (code) {
-  FauxtonAPI.dispatch({
+const updateMapCode = (code) => (dispatch) => {
+  dispatch({
     type: ActionTypes.VIEW_UPDATE_MAP_CODE,
     code: code
   });
-}
+};
 
-function updateReduceCode (code) {
-  FauxtonAPI.dispatch({
+const updateReduceCode = (code) => (dispatch) => {
+  dispatch({
     type: ActionTypes.VIEW_UPDATE_REDUCE_CODE,
     code: code
   });
-}
+};
 
-function selectDesignDoc (designDoc) {
-  FauxtonAPI.dispatch({
+const selectDesignDoc = (designDoc) => (dispatch) => {
+  dispatch({
     type: ActionTypes.DESIGN_DOC_CHANGE,
     options: {
       value: designDoc
     }
   });
-}
+};
 
-function updateNewDesignDocName (designDocName) {
-  FauxtonAPI.dispatch({
+const updateNewDesignDocName = (designDocName) => (dispatch) => {
+  dispatch({
     type: ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED,
     options: {
       value: designDocName
     }
   });
-}
+};
 
 // safely deletes an index of any type. It only deletes the actual design doc if there are no
 // other indexes of any type left in the doc
-function safeDeleteIndex (designDoc, designDocs, indexPropName, indexName, options) {
-  var opts = _.extend({
+const safeDeleteIndex = (designDoc, designDocs, indexPropName, indexName, options) => {
+  const opts = _.extend({
     onSuccess: function () { },
     onError: function (xhr) {
-      var responseText = JSON.parse(xhr.responseText).reason;
+      const responseText = JSON.parse(xhr.responseText).reason;
       FauxtonAPI.addNotification({
         msg: 'Delete failed: ' + responseText,
         type: 'error',
@@ -239,21 +238,21 @@
     }
   }, options);
 
-  var indexes = designDoc.get(indexPropName) || {};
+  const indexes = designDoc.get(indexPropName) || {};
   delete indexes[indexName];
-  var newIndexes = {};
+  const newIndexes = {};
   newIndexes[indexPropName] = indexes;
   designDoc.set(newIndexes);
 
   // we either save the design doc with the now-removed index, or we remove it altogether if there are no indexes
   // of any type left in the design doc
-  var indexTypePropNames = FauxtonAPI.getIndexTypePropNames();
-  var hasRemainingIndex = _.some(indexTypePropNames, function (propName) {
+  const indexTypePropNames = FauxtonAPI.getIndexTypePropNames();
+  const hasRemainingIndex = _.some(indexTypePropNames, function (propName) {
     return designDoc.get(propName) && _.keys(designDoc.get(propName)).length > 0;
   });
 
-  var promise;
-  var deleteDesignDoc = false;
+  let promise;
+  let deleteDesignDoc = false;
   if (hasRemainingIndex) {
     promise = designDoc.save();
   } else {
@@ -266,21 +265,21 @@
     }
     opts.onSuccess();
   }, opts.onError);
-}
+};
 
 
 
 // ---- helpers ----
 
-function findDesignDoc (designDocs, designDocName) {
+const findDesignDoc = (designDocs, designDocName) => {
   return designDocs.find(function (doc) {
     return doc.id === designDocName;
   }).dDocModel();
-}
+};
 
-function getDesignDoc (designDocs, targetDesignDocName, newDesignDocName, database) {
+const getDesignDoc = (designDocs, targetDesignDocName, newDesignDocName, database) => {
   if (targetDesignDocName === 'new-doc') {
-    var doc = {
+    const doc = {
       "_id": "_design/" + newDesignDocName,
       "views": {},
       "language": "javascript"
@@ -288,32 +287,32 @@
     return new Documents.Doc(doc, { database: database });
   }
 
-  var foundDoc = designDocs.find(function (ddoc) {
+  const foundDoc = designDocs.find(function (ddoc) {
     return ddoc.id === targetDesignDocName;
   });
   return (!foundDoc) ? null : foundDoc.dDocModel();
-}
+};
 
 
 export default {
   helpers: {
-    findDesignDoc: findDesignDoc,
-    getDesignDoc: getDesignDoc
+    findDesignDoc,
+    getDesignDoc
   },
-  safeDeleteIndex: safeDeleteIndex,
-  selectReduceChanged: selectReduceChanged,
-  changeViewName: changeViewName,
-  editIndex: editIndex,
-  clearIndex: clearIndex,
-  fetchDesignDocsBeforeEdit: fetchDesignDocsBeforeEdit,
-  shouldRemoveDdocView: shouldRemoveDdocView,
-  saveView: saveView,
-  addDesignDoc: addDesignDoc,
-  deleteView: deleteView,
-  cloneView: cloneView,
-  gotoEditViewPage: gotoEditViewPage,
-  updateMapCode: updateMapCode,
-  updateReduceCode: updateReduceCode,
-  selectDesignDoc: selectDesignDoc,
-  updateNewDesignDocName: updateNewDesignDocName
+  safeDeleteIndex,
+  selectReduceChanged,
+  changeViewName,
+  editIndex,
+  dispatchClearIndex,
+  dispatchFetchDesignDocsBeforeEdit,
+  shouldRemoveDdocView,
+  saveView,
+  addDesignDoc,
+  deleteView,
+  cloneView,
+  gotoEditViewPage,
+  updateMapCode,
+  updateReduceCode,
+  selectDesignDoc,
+  updateNewDesignDocName
 };
diff --git a/app/addons/documents/index-editor/components.js b/app/addons/documents/index-editor/components.js
index a96fab6..3cf69bd 100644
--- a/app/addons/documents/index-editor/components.js
+++ b/app/addons/documents/index-editor/components.js
@@ -10,15 +10,17 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import ReactComponents from "../../components/react-components";
+import ReactComponents from '../../components/react-components';
 import DesignDocSelector from './components/DesignDocSelector';
 import IndexEditor from './components/IndexEditor';
+import IndexEditorContainer from './components/IndexEditorContainer';
 import ReduceEditor from './components/ReduceEditor';
 
 const StyledSelect = ReactComponents.StyledSelect;
 
 export default {
-  EditorController: IndexEditor,
+  IndexEditorContainer,
+  IndexEditor,
   ReduceEditor,
   DesignDocSelector,
   StyledSelect
diff --git a/app/addons/documents/index-editor/components/DesignDocSelector.js b/app/addons/documents/index-editor/components/DesignDocSelector.js
index 956978f..57c6bbc 100644
--- a/app/addons/documents/index-editor/components/DesignDocSelector.js
+++ b/app/addons/documents/index-editor/components/DesignDocSelector.js
@@ -11,11 +11,9 @@
 // the License.
 
 import PropTypes from 'prop-types';
-
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import FauxtonAPI from "../../../../core/api";
-import ReactComponents from "../../../components/react-components";
+import React, { Component } from 'react';
+import FauxtonAPI from '../../../../core/api';
+import ReactComponents from '../../../components/react-components';
 
 const { StyledSelect } = ReactComponents;
 
diff --git a/app/addons/documents/index-editor/components/IndexEditor.js b/app/addons/documents/index-editor/components/IndexEditor.js
index 884858d..332b749 100644
--- a/app/addons/documents/index-editor/components/IndexEditor.js
+++ b/app/addons/documents/index-editor/components/IndexEditor.js
@@ -10,61 +10,27 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import app from "../../../../app";
-import FauxtonAPI from "../../../../core/api";
-import ReactComponents from "../../../components/react-components";
-import Stores from "../stores";
-import Actions from "../actions";
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import app from '../../../../app';
+import FauxtonAPI from '../../../../core/api';
+import ReactComponents from '../../../components/react-components';
 import DesignDocSelector from './DesignDocSelector';
 import ReduceEditor from './ReduceEditor';
 
 const getDocUrl = app.helpers.getDocUrl;
-const store = Stores.indexEditorStore;
 const {CodeEditorPanel, ConfirmButton, LoadLines} = ReactComponents;
 
 export default class IndexEditor extends Component {
 
   constructor(props) {
     super(props);
-    this.state = this.getStoreState();
-  }
-
-  getStoreState() {
-    return {
-      database: store.getDatabase(),
-      isNewView: store.isNewView(),
-      viewName: store.getViewName(),
-      designDocs: store.getDesignDocs(),
-      designDocList: store.getAvailableDesignDocs(),
-      originalViewName: store.getOriginalViewName(),
-      originalDesignDocName: store.getOriginalDesignDocName(),
-      newDesignDoc: store.isNewDesignDoc(),
-      designDocId: store.getDesignDocId(),
-      newDesignDocName: store.getNewDesignDocName(),
-      saveDesignDoc: store.getSaveDesignDoc(),
-      map: store.getMap(),
-      isLoading: store.isLoading()
-    };
-  }
-
-  onChange() {
-    this.setState(this.getStoreState());
-  }
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
   }
 
   // the code editor is a standalone component, so if the user goes from one edit view page to another, we need to
   // force an update of the editor panel
-  componentDidUpdate(prevProps, prevState) {
-    if (this.state.map !== prevState.map && this.mapEditor) {
+  componentDidUpdate(prevProps) {
+    if (this.props.map !== prevProps.map && this.mapEditor) {
       this.mapEditor.update();
     }
   }
@@ -76,31 +42,31 @@
       return;
     }
 
-    Actions.saveView({
-      database: this.state.database,
-      newView: this.state.isNewView,
-      viewName: this.state.viewName,
-      designDoc: this.state.saveDesignDoc,
-      designDocId: this.state.designDocId,
-      newDesignDoc: this.state.newDesignDoc,
-      originalViewName: this.state.originalViewName,
-      originalDesignDocName: this.state.originalDesignDocName,
+    this.props.saveView({
+      database: this.props.database,
+      isNewView: this.props.isNewView,
+      viewName: this.props.viewName,
+      designDoc: this.props.saveDesignDoc,
+      designDocId: this.props.designDocId,
+      isNewDesignDoc: this.props.isNewDesignDoc,
+      originalViewName: this.props.originalViewName,
+      originalDesignDocName: this.props.originalDesignDocName,
       map: this.mapEditor.getValue(),
       reduce: this.reduceEditor.getReduceValue(),
-      designDocs: this.state.designDocs
+      designDocs: this.props.designDocs
     });
   }
 
   viewChange(el) {
-    Actions.changeViewName(el.target.value);
+    this.props.changeViewName(el.target.value);
   }
 
   updateMapCode(code) {
-    Actions.updateMapCode(code);
+    this.props.updateMapCode(code);
   }
 
   render() {
-    if (this.state.isLoading) {
+    if (this.props.isLoading) {
       return (
         <div className="define-view">
           <LoadLines />
@@ -108,9 +74,9 @@
       );
     }
 
-    const pageHeader = (this.state.isNewView) ? 'New View' : 'Edit View';
-    const btnLabel = (this.state.isNewView) ? 'Create Document and then Build Index' : 'Save Document and then Build Index';
-    const cancelLink = '#' + FauxtonAPI.urls('view', 'showView', this.state.database.id, this.state.designDocId, this.state.viewName);
+    const pageHeader = (this.props.isNewView) ? 'New View' : 'Edit View';
+    const btnLabel = (this.props.isNewView) ? 'Create Document and then Build Index' : 'Save Document and then Build Index';
+    const cancelLink = '#' + FauxtonAPI.urls('view', 'showView', this.props.database.id, this.props.designDocId, this.props.viewName);
     return (
       <div className="define-view" >
         <form className="form-horizontal view-query-save" onSubmit={this.saveView.bind(this)}>
@@ -119,11 +85,11 @@
           <div className="new-ddoc-section">
             <DesignDocSelector
               ref={(el) => { this.designDocSelector = el; }}
-              designDocList={this.state.designDocList}
-              selectedDesignDocName={this.state.designDocId}
-              newDesignDocName={this.state.newDesignDocName}
-              onSelectDesignDoc={Actions.selectDesignDoc}
-              onChangeNewDesignDocName={Actions.updateNewDesignDocName}
+              designDocList={this.props.designDocList}
+              selectedDesignDocName={this.props.designDocId}
+              newDesignDocName={this.props.newDesignDocName}
+              onSelectDesignDoc={this.props.selectDesignDoc}
+              onChangeNewDesignDocName={this.props.updateNewDesignDocName}
               docLink={getDocUrl('DESIGN_DOCS')} />
           </div>
 
@@ -142,7 +108,7 @@
             <input
               type="text"
               id="index-name"
-              value={this.state.viewName}
+              value={this.props.viewName}
               onChange={this.viewChange.bind(this)}
               placeholder="Index name" />
           </div>
@@ -153,8 +119,8 @@
             docLink={getDocUrl('MAP_FUNCS')}
             blur={this.updateMapCode.bind(this)}
             allowZenMode={false}
-            defaultCode={this.state.map} />
-          <ReduceEditor ref={(el) => { this.reduceEditor = el; }} />
+            defaultCode={this.props.map} />
+          <ReduceEditor ref={(el) => { this.reduceEditor = el; }} {...this.props} />
           <div className="padded-box">
             <div className="control-group">
               <ConfirmButton id="save-view" text={btnLabel} />
@@ -166,3 +132,19 @@
     );
   }
 }
+
+IndexEditor.propTypes = {
+  isLoading:PropTypes.bool.isRequired,
+  isNewView: PropTypes.bool.isRequired,
+  database: PropTypes.object.isRequired,
+  designDocId: PropTypes.string.isRequired,
+  viewName: PropTypes.string.isRequired,
+  isNewDesignDoc: PropTypes.bool.isRequired,
+  originalViewName: PropTypes.string,
+  originalDesignDocName: PropTypes.string,
+  designDocs: PropTypes.object,
+  saveDesignDoc: PropTypes.object,
+  updateNewDesignDocName: PropTypes.func.isRequired,
+  changeViewName: PropTypes.func.isRequired,
+  updateMapCode: PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-editor/components/IndexEditorContainer.js b/app/addons/documents/index-editor/components/IndexEditorContainer.js
new file mode 100644
index 0000000..445b029
--- /dev/null
+++ b/app/addons/documents/index-editor/components/IndexEditorContainer.js
@@ -0,0 +1,78 @@
+// Licensed under the Apache License, Version 2.0 (the 'License'); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { connect } from 'react-redux';
+import IndexEditor from './IndexEditor';
+import Actions from '../actions';
+import { getSaveDesignDoc, getDesignDocIds, reduceSelectedOption, hasCustomReduce } from '../reducers';
+
+const mapStateToProps = ({ indexEditor }) => {
+  return {
+    database: indexEditor.database,
+    isNewView: indexEditor.isNewView,
+    viewName: indexEditor.viewName,
+    designDocs: indexEditor.designDocs,
+    designDocList: getDesignDocIds(indexEditor),
+    originalViewName: indexEditor.originalViewName,
+    originalDesignDocName: indexEditor.originalDesignDocName,
+    isNewDesignDoc: indexEditor.isNewDesignDoc,
+    designDocId: indexEditor.designDocId,
+    newDesignDocName: indexEditor.newDesignDocName,
+    saveDesignDoc: getSaveDesignDoc(indexEditor),
+    map: indexEditor.view.map,
+    isLoading: indexEditor.isLoading,
+    reduce: indexEditor.view.reduce,
+    reduceOptions: indexEditor.reduceOptions,
+    reduceSelectedOption: reduceSelectedOption(indexEditor),
+    hasCustomReduce: hasCustomReduce(indexEditor),
+    hasReduce: !!indexEditor.view.reduce
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    saveView: (viewInfo) => {
+      dispatch(Actions.saveView(viewInfo));
+    },
+
+    changeViewName: (name) => {
+      dispatch(Actions.changeViewName(name));
+    },
+
+    updateMapCode: (code) => {
+      dispatch(Actions.updateMapCode(code));
+    },
+
+    selectDesignDoc: (designDoc) => {
+      dispatch(Actions.selectDesignDoc(designDoc));
+    },
+
+    updateNewDesignDocName: (designDocName) => {
+      dispatch(Actions.updateNewDesignDocName(designDocName));
+    },
+
+    updateReduceCode: (code) => {
+      dispatch(Actions.updateReduceCode(code));
+    },
+
+    selectReduceChanged: (reduceOption) => {
+      dispatch(Actions.selectReduceChanged(reduceOption));
+    }
+  };
+};
+
+const IndexEditorContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(IndexEditor);
+
+export default IndexEditorContainer;
diff --git a/app/addons/documents/index-editor/components/ReduceEditor.js b/app/addons/documents/index-editor/components/ReduceEditor.js
index 7e0b181..7f1df52 100644
--- a/app/addons/documents/index-editor/components/ReduceEditor.js
+++ b/app/addons/documents/index-editor/components/ReduceEditor.js
@@ -10,59 +10,33 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import app from "../../../../app";
-import ReactComponents from "../../../components/react-components";
-import Stores from "../stores";
-import Actions from "../actions";
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import app from '../../../../app';
+import ReactComponents from '../../../components/react-components';
 
 const getDocUrl = app.helpers.getDocUrl;
-const store = Stores.indexEditorStore;
 const {CodeEditorPanel, StyledSelect} = ReactComponents;
 
 export default class ReduceEditor extends Component {
 
   constructor(props) {
     super(props);
-    this.state = this.getStoreState();
-  }
-
-  onChange() {
-    this.setState(this.getStoreState());
-  }
-
-  componentDidMount() {
-    store.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    store.off('change', this.onChange);
-  }
-
-  getStoreState() {
-    return {
-      reduce: store.getReduce(),
-      reduceOptions: store.reduceOptions(),
-      reduceSelectedOption: store.reduceSelectedOption(),
-      hasCustomReduce: store.hasCustomReduce(),
-      hasReduce: store.hasReduce()
-    };
   }
 
   getOptionsList() {
-    return _.map(this.state.reduceOptions, (reduce, i) => {
+    return this.props.reduceOptions.map((reduce, i) => {
       return <option key={i} value={reduce}>{reduce}</option>;
     });
   }
 
   getReduceValue() {
-    if (!this.state.hasReduce) {
+    if (!this.props.hasReduce) {
       return null;
     }
 
-    if (!this.state.hasCustomReduce) {
-      return this.state.reduce;
+    if (!this.props.hasCustomReduce) {
+      return this.props.reduce;
     }
 
     return this.reduceEditor.getValue();
@@ -73,23 +47,23 @@
   }
 
   updateReduceCode(code) {
-    Actions.updateReduceCode(code);
+    this.props.updateReduceCode(code);
   }
 
   selectChange(event) {
-    Actions.selectReduceChanged(event.target.value);
+    this.props.selectReduceChanged(event.target.value);
   }
 
   render() {
     const reduceOptions = this.getOptionsList();
     let customReduceSection;
 
-    if (this.state.hasCustomReduce) {
+    if (this.props.hasCustomReduce) {
       customReduceSection = <CodeEditorPanel
         ref={node => this.reduceEditor = node}
         id='reduce-function'
         title={'Custom Reduce function'}
-        defaultCode={this.state.reduce}
+        defaultCode={this.props.reduce}
         allowZenMode={false}
         blur={this.updateReduceCode.bind(this)}
       />;
@@ -114,10 +88,20 @@
             selectContent={reduceOptions}
             selectChange={this.selectChange.bind(this)}
             selectId="reduce-function-selector"
-            selectValue={this.state.reduceSelectedOption} />
+            selectValue={this.props.reduceSelectedOption} />
         </div>
         {customReduceSection}
       </div>
     );
   }
 }
+
+ReduceEditor.propTypes = {
+  reduceOptions: PropTypes.array.isRequired,
+  hasReduce: PropTypes.bool.isRequired,
+  hasCustomReduce: PropTypes.bool.isRequired,
+  reduce: PropTypes.string,
+  reduceSelectedOption: PropTypes.string.isRequired,
+  updateReduceCode: PropTypes.func.isRequired,
+  selectReduceChanged: PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-editor/reducers.js b/app/addons/documents/index-editor/reducers.js
new file mode 100644
index 0000000..663818f
--- /dev/null
+++ b/app/addons/documents/index-editor/reducers.js
@@ -0,0 +1,196 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import ActionTypes from './actiontypes';
+import Resources from "../resources";
+
+const defaultMap = 'function (doc) {\n  emit(doc._id, 1);\n}';
+const defaultReduce = 'function (keys, values, rereduce) {\n  if (rereduce) {\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}';
+const builtInReducers = ['_sum', '_count', '_stats'];
+
+const initialState = {
+  designDocs: new Backbone.Collection(),
+  isLoading: true,
+  view: { reduce: '', map: defaultMap },
+  database: { id: '0' },
+  designDocId: '',
+  isNewDesignDoc: false,
+  newDesignDocName: '',
+  isNewView: false,
+  viewName: '',
+  originalViewName: '',
+  originalDesignDocName: '',
+  reduceOptions: builtInReducers.concat(['CUSTOM', 'NONE'])
+};
+
+function editIndex(state, options) {
+  const newState = {
+    ...state,
+    isLoading: false,
+    newDesignDocName: '',
+    isNewView: options.isNewView,
+    viewName: options.viewName || 'viewName',
+    isNewDesignDoc: options.isNewDesignDoc || false,
+    designDocs: options.designDocs,
+    designDocId: options.designDocId,
+    originalDesignDocName: options.designDocId,
+    database: options.database
+  };
+  newState.originalViewName = newState.viewName;
+  newState.view = getView(newState);
+  return newState;
+}
+
+function getView(state) {
+  if (state.isNewView || state.isNewDesignDoc) {
+    return { reduce: '', map: defaultMap };
+  }
+
+  const designDoc = state.designDocs.find(ddoc => {
+    return state.designDocId == ddoc.id;
+  }).dDocModel();
+  return designDoc.get('views')[state.viewName];
+}
+
+export function reduceSelectedOption(state) {
+  if (!state.view.reduce) {
+    return 'NONE';
+  }
+  if (hasCustomReduce(state)) {
+    return 'CUSTOM';
+  }
+  return state.view.reduce;
+}
+
+export function hasCustomReduce(state) {
+  if (state.view.reduce) {
+    return !builtInReducers.includes(state.view.reduce);
+  }
+  return false;
+}
+
+export function getSaveDesignDoc(state) {
+  if (state.designDocId === 'new-doc') {
+    const doc = {
+      _id: '_design/' + state.newDesignDocName,
+      views: {},
+      language: 'javascript'
+    };
+    return new Resources.Doc(doc, { database: state.database });
+  }
+
+  if (!state.designDocs) {
+    return null;
+  }
+
+  const foundDoc = state.designDocs.find(ddoc => {
+    return ddoc.id === state.designDocId;
+  });
+
+  return foundDoc ? foundDoc.dDocModel() : null;
+}
+
+// returns a simple array of design doc IDs. Omits mango docs
+export function getDesignDocIds(state) {
+  if (!state.designDocs) {
+    return [];
+  }
+  return state.designDocs.filter(doc => {
+    return !doc.isMangoDoc();
+  }).map(doc => {
+    return doc.id;
+  });
+}
+
+export default function indexEditor(state = initialState, action) {
+  const { options } = action;
+  switch (action.type) {
+
+    case ActionTypes.CLEAR_INDEX:
+      return {
+        ...initialState
+      };
+
+    case ActionTypes.EDIT_INDEX:
+      return editIndex(state, options);
+
+    case ActionTypes.EDIT_NEW_INDEX:
+      return editIndex(state, options);
+
+    case ActionTypes.VIEW_NAME_CHANGE:
+      return {
+        ...state,
+        viewName: action.name
+      };
+
+    case ActionTypes.SELECT_REDUCE_CHANGE:
+      let newReduce = action.reduceSelectedOption;
+      if (newReduce === 'NONE') {
+        newReduce = '';
+      }
+      if (newReduce === 'CUSTOM') {
+        newReduce = defaultReduce;
+      }
+      return {
+        ...state,
+        view: {
+          ...state.view,
+          reduce: newReduce
+        }
+      };
+
+    case ActionTypes.DESIGN_DOC_CHANGE:
+      return {
+        ...state,
+        designDocId: options.value
+      };
+
+    case ActionTypes.VIEW_SAVED:
+      return state;
+
+    case ActionTypes.VIEW_CREATED:
+      return state;
+
+    case ActionTypes.VIEW_ADD_DESIGN_DOC:
+      return {
+        ...state,
+        designDocId: action.designDoc._id
+      };
+
+    case ActionTypes.VIEW_UPDATE_MAP_CODE:
+      return {
+        ...state,
+        view: {
+          ...state.view,
+          map: action.code
+        }
+      };
+
+    case ActionTypes.VIEW_UPDATE_REDUCE_CODE:
+      return {
+        ...state,
+        view: {
+          ...state.view,
+          reduce: action.code
+        }
+      };
+
+    case ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED:
+      return {
+        ...state,
+        newDesignDocName: options.value
+      };
+
+    default:
+      return state;
+  }
+}
diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js
deleted file mode 100644
index c5b6205..0000000
--- a/app/addons/documents/index-editor/stores.js
+++ /dev/null
@@ -1,264 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "./actiontypes";
-import Resources from "../resources";
-var Stores = {};
-
-Stores.IndexEditorStore = FauxtonAPI.Store.extend({
-
-  defaultMap: 'function (doc) {\n  emit(doc._id, 1);\n}',
-  defaultReduce: 'function (keys, values, rereduce) {\n  if (rereduce) {\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}',
-
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._designDocs = [];
-    this._isLoading = true;
-    this._view = { reduce: '', map: this.defaultMap };
-    this._database = { id: '0' };
-  },
-
-  editIndex: function (options) {
-    this._database = options.database;
-    this._newView = options.newView;
-    this._viewName = options.viewName || 'viewName';
-    this._newDesignDoc = options.newDesignDoc || false;
-    this._newDesignDocName = '';
-    this._designDocs = options.designDocs;
-    this._designDocId = options.designDocId;
-    this._originalViewName = this._viewName;
-    this._originalDesignDocName = options.designDocId;
-    this.setView();
-
-    this._isLoading = false;
-  },
-
-  isLoading: function () {
-    return this._isLoading;
-  },
-
-  setView: function () {
-    if (this._newView || this._newDesignDoc) {
-      this._view = { reduce: '', map: this.defaultMap };
-    } else {
-      this._view = this.getDesignDoc().get('views')[this._viewName];
-    }
-  },
-
-  getDatabase: function () {
-    return this._database;
-  },
-
-  getMap: function () {
-    return this._view.map;
-  },
-
-  setMap: function (map) {
-    this._view.map = map;
-  },
-
-  getReduce: function () {
-    return this._view.reduce;
-  },
-
-  setReduce: function (reduce) {
-    this._view.reduce = reduce;
-  },
-
-  getDesignDoc: function () {
-    return this._designDocs.find((ddoc) => {
-      return this._designDocId == ddoc.id;
-    }).dDocModel();
-  },
-
-  getDesignDocs: function () {
-    return this._designDocs;
-  },
-
-  // returns a simple array of design doc IDs. Omits mango docs
-  getAvailableDesignDocs: function () {
-    var availableDocs = this.getDesignDocs().filter(function (doc) {
-      return !doc.isMangoDoc();
-    });
-    return _.map(availableDocs, function (doc) {
-      return doc.id;
-    });
-  },
-
-  getDesignDocId: function () {
-    return this._designDocId;
-  },
-
-  setDesignDocId: function (designDocId) {
-    this._designDocId = designDocId;
-  },
-
-  isNewDesignDoc: function () {
-    return this._newDesignDoc;
-  },
-
-  isNewView: function () {
-    return this._newView;
-  },
-
-  getViewName: function () {
-    return this._viewName;
-  },
-
-  setViewName: function (name) {
-    this._viewName = name;
-  },
-
-  hasCustomReduce: function () {
-    if (!this.hasReduce()) {
-      return false;
-    }
-    return !_.includes(this.builtInReduces(), this.getReduce());
-  },
-
-  hasReduce: function () {
-    if (!this.getReduce()) {
-      return false;
-    }
-    return true;
-  },
-
-  getOriginalViewName: function () {
-    return this._originalViewName;
-  },
-
-  getOriginalDesignDocName: function () {
-    return this._originalDesignDocName;
-  },
-
-  builtInReduces: function () {
-    return ['_sum', '_count', '_stats'];
-  },
-
-  reduceSelectedOption: function () {
-    if (!this.hasReduce()) {
-      return 'NONE';
-    }
-    if (this.hasCustomReduce()) {
-      return 'CUSTOM';
-    }
-    return this.getReduce();
-  },
-
-  reduceOptions: function () {
-    return this.builtInReduces().concat(['CUSTOM', 'NONE']);
-  },
-
-  updateReduceFromSelect: function (selectedReduce) {
-    if (selectedReduce === 'NONE') {
-      this.setReduce(null);
-      return;
-    }
-    if (selectedReduce === 'CUSTOM') {
-      this.setReduce(this.defaultReduce);
-      return;
-    }
-    this.setReduce(selectedReduce);
-  },
-
-  addDesignDoc: function (designDoc) {
-    this._designDocs.add(designDoc, { merge: true });
-    this._designDocId = designDoc._id;
-  },
-
-  getNewDesignDocName: function () {
-    return this._newDesignDocName;
-  },
-
-  getSaveDesignDoc: function () {
-    if (this._designDocId === 'new-doc') {
-      var doc = {
-        _id: '_design/' + this._newDesignDocName,
-        views: {},
-        language: 'javascript'
-      };
-      return new Resources.Doc(doc, { database: this._database });
-    }
-
-    var foundDoc = this._designDocs.find(function (ddoc) {
-      return ddoc.id === this._designDocId;
-    }.bind(this));
-
-    return (!foundDoc) ? null : foundDoc.dDocModel();
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-      case ActionTypes.CLEAR_INDEX:
-        this.reset();
-        break;
-
-      case ActionTypes.EDIT_INDEX:
-        this.editIndex(action.options);
-        break;
-
-      case ActionTypes.VIEW_NAME_CHANGE:
-        this.setViewName(action.name);
-        break;
-
-      case ActionTypes.EDIT_NEW_INDEX:
-        this.editIndex(action.options);
-        break;
-
-      case ActionTypes.SELECT_REDUCE_CHANGE:
-        this.updateReduceFromSelect(action.reduceSelectedOption);
-        break;
-
-      case ActionTypes.DESIGN_DOC_CHANGE:
-        this.setDesignDocId(action.options.value);
-        break;
-
-      case ActionTypes.VIEW_SAVED:
-        break;
-
-      case ActionTypes.VIEW_CREATED:
-        break;
-
-      case ActionTypes.VIEW_ADD_DESIGN_DOC:
-        this.addDesignDoc(action.designDoc);
-        this.setView();
-        break;
-
-      case ActionTypes.VIEW_UPDATE_MAP_CODE:
-        this.setMap(action.code);
-        break;
-
-      case ActionTypes.VIEW_UPDATE_REDUCE_CODE:
-        this.setReduce(action.code);
-        break;
-
-      case ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED:
-        this._newDesignDocName = action.options.value;
-        break;
-
-      default:
-        return;
-    }
-
-    this.triggerChange();
-  }
-
-});
-
-Stores.indexEditorStore = new Stores.IndexEditorStore();
-Stores.indexEditorStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.indexEditorStore.dispatch.bind(Stores.indexEditorStore));
-
-export default Stores;
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 4be1d20..7f91c8e 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -247,7 +247,7 @@
   dbName, dropDownLinks, selectedNavItem, designDocInfo, partitionKey }) => {
 
   const content = showEditView ?
-    <IndexEditorComponents.EditorController /> :
+    <IndexEditorComponents.IndexEditorContainer /> :
     <DesignDocInfoContainer
       designDocInfo={designDocInfo}
       designDocName={selectedNavItem.designDocName}/>;
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 8d357ba..bfe9c42 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -74,10 +74,10 @@
   showView: function (databaseName, partitionKey, ddoc, viewName) {
     viewName = viewName.replace(/\?.*$/, '');
 
-    ActionsIndexEditor.clearIndex();
-    ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+    ActionsIndexEditor.dispatchClearIndex();
+    ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
       viewName: viewName,
-      newView: false,
+      isNewView: false,
       database: this.database,
       designDocs: this.designDocs,
       designDocId: '_design/' + ddoc
@@ -127,21 +127,21 @@
   },
 
   createView: function (database, partitionKey, _designDoc) {
-    let newDesignDoc = true;
+    let isNewDesignDoc = true;
     let designDoc = 'new-doc';
 
     if (_designDoc) {
       designDoc = '_design/' + _designDoc;
-      newDesignDoc = false;
+      isNewDesignDoc = false;
     }
 
-    ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+    ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
       viewName: 'new-view',
-      newView: true,
+      isNewView: true,
       database: this.database,
       designDocs: this.designDocs,
       designDocId: designDoc,
-      newDesignDoc: newDesignDoc
+      isNewDesignDoc: isNewDesignDoc
     });
 
     const selectedNavItem = new SidebarItemSelection('');
@@ -163,9 +163,9 @@
   },
 
   editView: function (databaseName, partitionKey, ddocName, viewName) {
-    ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+    ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
       viewName: viewName,
-      newView: false,
+      isNewView: false,
       database: this.database,
       designDocs: this.designDocs,
       designDocId: '_design/' + ddocName