diff --git a/__tests__/src/components/WindowViewer.test.js b/__tests__/src/components/WindowViewer.test.js
index 8414de7fc16593d5e4df5590747add73f5dc5442..373ca1d41366e07eef2a0b194a9d89f0056bff55 100644
--- a/__tests__/src/components/WindowViewer.test.js
+++ b/__tests__/src/components/WindowViewer.test.js
@@ -1,24 +1,13 @@
 import React from 'react';
 import { shallow } from 'enzyme';
-import { Utils } from 'manifesto.js/dist-esmodule/Utils';
 import { WindowViewer } from '../../../src/components/WindowViewer';
 import OSDViewer from '../../../src/containers/OpenSeadragonViewer';
 import WindowCanvasNavigationControls from '../../../src/containers/WindowCanvasNavigationControls';
-import fixture from '../../fixtures/version-2/019.json';
-import emptyCanvasFixture from '../../fixtures/version-2/emptyCanvas.json';
-
-let currentCanvases = Utils.parseManifest(fixture).getSequences()[0].getCanvases();
 
 /** create wrapper */
 function createWrapper(props) {
   return shallow(
     <WindowViewer
-      canvasIndex={0}
-      canvasLabel="label"
-      infoResponses={{}}
-      fetchInfoResponse={() => {}}
-      currentCanvases={[currentCanvases[1]]}
-      view="single"
       windowId="xyz"
       {...props}
     />,
@@ -35,111 +24,4 @@ describe('WindowViewer', () => {
       </OSDViewer>,
     )).toBe(true);
   });
-  describe('currentInfoResponses', () => {
-    describe('returns only available infoResponses', () => {
-      it('isFetching is false', () => {
-        wrapper = createWrapper(
-          {
-            currentCanvasId: 1,
-            infoResponses: {
-              'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': {
-                isFetching: false,
-              },
-              'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410': {
-                isFetching: true,
-              },
-            },
-            view: 'book',
-          },
-        );
-        expect(wrapper.instance().currentInfoResponses().length).toEqual(1);
-      });
-      it('infoResponse is undefined', () => {
-        wrapper = createWrapper(
-          {
-            currentCanvasId: 1,
-            infoResponses: {
-              foo: {
-                isFetching: false,
-              },
-              'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': {
-                isFetching: false,
-              },
-            },
-            view: 'book',
-          },
-        );
-        expect(wrapper.instance().currentInfoResponses().length).toEqual(1);
-      });
-      it('error is not present', () => {
-        wrapper = createWrapper(
-          {
-            currentCanvasId: 1,
-            infoResponses: {
-              'https://stacks.stanford.edu/image/iiif/fr426cg9537%2FSC1094_s3_b14_f17_Cats_1976_0005': {
-                isFetching: false,
-              },
-              'https://stacks.stanford.edu/image/iiif/rz176rt6531%2FPC0170_s3_Tree_Calendar_20081101_152516_0410': {
-                error: 'yikes!',
-                isFetching: false,
-              },
-            },
-            view: 'book',
-          },
-        );
-        expect(wrapper.instance().currentInfoResponses().length).toEqual(1);
-      });
-    });
-  });
-
-  describe('componentDidMount', () => {
-    it('does not call fetchInfoResponse for a canvas that has no images', () => {
-      const mockFnCanvas0 = jest.fn();
-      const mockFnCanvas2 = jest.fn();
-      const canvases = Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases();
-
-      currentCanvases = [canvases[0]];
-
-      wrapper = createWrapper(
-        {
-          currentCanvases,
-          currentCanvasId: 0,
-          fetchInfoResponse: mockFnCanvas0,
-          view: 'single',
-        },
-      );
-      expect(mockFnCanvas0).toHaveBeenCalledTimes(1);
-
-      currentCanvases = [canvases[2]];
-      wrapper = createWrapper(
-        {
-          currentCanvases,
-          currentCanvasId: 2,
-          fetchInfoResponse: mockFnCanvas2,
-          view: 'single',
-        },
-      );
-      expect(mockFnCanvas2).toHaveBeenCalledTimes(0);
-    });
-  });
-
-  describe('componentDidUpdate', () => {
-    it('does not call fetchInfoResponse for a canvas that has no images', () => {
-      const mockFn = jest.fn();
-      const canvases = Utils.parseManifest(emptyCanvasFixture).getSequences()[0].getCanvases();
-      currentCanvases = [canvases[2]];
-      wrapper = createWrapper(
-        {
-          currentCanvases,
-          currentCanvasId: 2,
-          fetchInfoResponse: mockFn,
-          view: 'single',
-        },
-      );
-
-      wrapper.setProps({ currentCanvases: [canvases[3]], currentCanvasId: 3, view: 'single' });
-
-      expect(mockFn).toHaveBeenCalledTimes(0);
-    });
-  });
 });
diff --git a/__tests__/src/sagas/windows.test.js b/__tests__/src/sagas/windows.test.js
index b557fda944ebf815500d18394bbfdd6fcb2bb84a..772051fa976ff57e3b80b9ecf104fd4f461ddc3f 100644
--- a/__tests__/src/sagas/windows.test.js
+++ b/__tests__/src/sagas/windows.test.js
@@ -13,6 +13,7 @@ import {
   getSelectedContentSearchAnnotationIds,
   getSortedSearchAnnotationsForCompanionWindow,
   getVisibleCanvasIds, getCanvasForAnnotation,
+  getCanvases, selectInfoResponses,
 } from '../../../src/state/selectors';
 import { fetchManifest } from '../../../src/state/sagas/iiif';
 import {
@@ -25,6 +26,7 @@ import {
   setCanvasforSelectedAnnotation,
   panToFocusedWindow,
   setCurrentAnnotationsOnCurrentCanvas,
+  fetchInfoResponses,
 } from '../../../src/state/sagas/windows';
 import fixture from '../../fixtures/version-2/019.json';
 
@@ -398,4 +400,50 @@ describe('window-level sagas', () => {
         .run().then(({ allEffects }) => allEffects.length === 0);
     });
   });
+
+  describe('fetchInfoResponses', () => {
+    it('requests info responses for each visible canvas', () => {
+      const action = {
+        visibleCanvases: ['http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json'],
+        windowId: 'foo',
+      };
+
+      const manifest = Utils.parseManifest(fixture);
+
+      return expectSaga(fetchInfoResponses, action)
+        .provide([
+          [select(getCanvases, { windowId: 'foo' }), manifest.getSequences()[0].getCanvases()],
+          [select(selectInfoResponses), {}],
+        ])
+        .put.like({
+          action: {
+            infoId: 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44',
+            type: 'mirador/REQUEST_INFO_RESPONSE',
+          },
+        })
+        .run();
+    });
+
+    it('requests nothing if the response is  already in the store', () => {
+      const action = {
+        visibleCanvases: ['http://iiif.io/api/presentation/2.0/example/fixtures/canvas/24/c1.json'],
+        windowId: 'foo',
+      };
+
+      const manifest = Utils.parseManifest(fixture);
+
+      return expectSaga(fetchInfoResponses, action)
+        .provide([
+          [select(getCanvases, { windowId: 'foo' }), manifest.getSequences()[0].getCanvases()],
+          [select(selectInfoResponses), { 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44': {} }],
+        ])
+        .not.put.like({
+          action: {
+            infoId: 'https://stacks.stanford.edu/image/iiif/hg676jb4964%2F0380_796-44',
+            type: 'mirador/REQUEST_INFO_RESPONSE',
+          },
+        })
+        .run();
+    });
+  });
 });
diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js
index beeba092c5197419ef70a1961de71761133bd95c..7a7d1acc697ea00338b6ce34db34dad0c2316cb9 100644
--- a/src/components/WindowViewer.js
+++ b/src/components/WindowViewer.js
@@ -1,10 +1,7 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
-import difference from 'lodash/difference';
-import flatten from 'lodash/flatten';
 import OSDViewer from '../containers/OpenSeadragonViewer';
 import WindowCanvasNavigationControls from '../containers/WindowCanvasNavigationControls';
-import MiradorCanvas from '../lib/MiradorCanvas';
 
 /**
  * Represents a WindowViewer in the mirador workspace. Responsible for mounting
@@ -23,91 +20,6 @@ export class WindowViewer extends Component {
     return { hasError: true };
   }
 
-  /**
-   * componentDidMount - React lifecycle method
-   * Request the initial canvas on mount
-   */
-  componentDidMount() {
-    const {
-      currentCanvases, fetchInfoResponse,
-    } = this.props;
-
-    if (!this.infoResponseIsInStore()) {
-      currentCanvases.forEach((canvas) => {
-        const miradorCanvas = new MiradorCanvas(canvas);
-        miradorCanvas.iiifImageResources.forEach((imageResource) => {
-          fetchInfoResponse({ imageResource });
-        });
-      });
-    }
-  }
-
-  /**
-   * componentDidUpdate - React lifecycle method
-   * Request a new canvas if it is needed
-   */
-  componentDidUpdate(prevProps) {
-    const {
-      currentCanvases, fetchInfoResponse,
-    } = this.props;
-
-    if (difference(currentCanvases, prevProps.currentCanvases).length > 0
-    && !this.infoResponseIsInStore()) {
-      currentCanvases.forEach((canvas) => {
-        const miradorCanvas = new MiradorCanvas(canvas);
-        miradorCanvas.iiifImageResources.forEach((imageResource) => {
-          fetchInfoResponse({ imageResource });
-        });
-      });
-    }
-  }
-
-  /**
-   * infoResponseIsInStore - checks whether or not an info response is already
-   * in the store. No need to request it again.
-   * @return [Boolean]
-   */
-  infoResponseIsInStore() {
-    const responses = this.currentInfoResponses();
-    if (responses.length === this.imageServiceIds().length) {
-      return true;
-    }
-    return false;
-  }
-
-  /** */
-  imageServiceIds() {
-    const { currentCanvases } = this.props;
-
-    return flatten(currentCanvases.map(canvas => new MiradorCanvas(canvas).imageServiceIds));
-  }
-
-  /**
-   * currentInfoResponses - Selects infoResponses that are relevent to existing
-   * canvases to be displayed.
-   */
-  currentInfoResponses() {
-    const { infoResponses } = this.props;
-
-    return this.imageServiceIds().map(imageId => (
-      infoResponses[imageId]
-    )).filter(infoResponse => (infoResponse !== undefined
-      && infoResponse.isFetching === false
-      && infoResponse.error === undefined));
-  }
-
-  /**
-   * Return an image information response from the store for the correct image
-   */
-  infoResponsesFetchedFromStore() {
-    const responses = this.currentInfoResponses();
-    // Only return actual tileSources when all current canvases have completed.
-    if (responses.length === this.imageServiceIds().length) {
-      return responses;
-    }
-    return [];
-  }
-
   /**
    * Renders things
    */
@@ -122,20 +34,14 @@ export class WindowViewer extends Component {
 
     return (
       <OSDViewer
-        infoResponses={this.infoResponsesFetchedFromStore()}
         windowId={windowId}
       >
-        <WindowCanvasNavigationControls key="canvas_nav" windowId={windowId} />
+        <WindowCanvasNavigationControls windowId={windowId} />
       </OSDViewer>
     );
   }
 }
 
 WindowViewer.propTypes = {
-  currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
-  fetchAnnotation: PropTypes.func.isRequired,
-  fetchInfoResponse: PropTypes.func.isRequired,
-  infoResponses: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
-  receiveAnnotation: PropTypes.func.isRequired,
   windowId: PropTypes.string.isRequired,
 };
diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js
index 96faa482966383f25357f297faa3ed8742b7e7b7..c733eb33bce2be58a22f31805de13239a1ca870c 100644
--- a/src/containers/OpenSeadragonViewer.js
+++ b/src/containers/OpenSeadragonViewer.js
@@ -2,6 +2,7 @@ import { compose } from 'redux';
 import { connect } from 'react-redux';
 import { withTranslation } from 'react-i18next';
 import { withStyles } from '@material-ui/core/styles';
+import flatten from 'lodash/flatten';
 import { withPlugins } from '../extend/withPlugins';
 import { OpenSeadragonViewer } from '../components/OpenSeadragonViewer';
 import * as actions from '../state/actions';
@@ -14,6 +15,7 @@ import {
   getViewer,
   getConfig,
   getCompanionWindowsForContent,
+  selectInfoResponses,
 } from '../state/selectors';
 
 /**
@@ -21,19 +23,30 @@ import {
  * @memberof Window
  * @private
  */
-const mapStateToProps = (state, { windowId }) => ({
-  canvasWorld: new CanvasWorld(
+const mapStateToProps = (state, { windowId }) => {
+  const canvasWorld = new CanvasWorld(
     getVisibleCanvases(state, { windowId }),
     getLayersForVisibleCanvases(state, { windowId }),
     getSequenceViewingDirection(state, { windowId }),
-  ),
-  drawAnnotations: getConfig(state).window.forceDrawAnnotations
-    || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0
-    || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0,
-  nonTiledImages: getVisibleCanvasNonTiledResources(state, { windowId }),
-  osdConfig: state.config.osdConfig,
-  viewerConfig: getViewer(state, { windowId }),
-});
+  );
+
+  const infoResponses = selectInfoResponses(state);
+  const imageServiceIds = flatten(canvasWorld.canvases.map(c => c.imageServiceIds));
+
+  return {
+    canvasWorld,
+    drawAnnotations: getConfig(state).window.forceDrawAnnotations
+      || getCompanionWindowsForContent(state, { content: 'annotations', windowId }).length > 0
+      || getCompanionWindowsForContent(state, { content: 'search', windowId }).length > 0,
+    infoResponses: imageServiceIds.map(id => infoResponses[id])
+      .filter(infoResponse => (infoResponse !== undefined
+        && infoResponse.isFetching === false
+        && infoResponse.error === undefined)),
+    nonTiledImages: getVisibleCanvasNonTiledResources(state, { windowId }),
+    osdConfig: state.config.osdConfig,
+    viewerConfig: getViewer(state, { windowId }),
+  };
+};
 
 /**
  * mapDispatchToProps - used to hook up connect to action creators
diff --git a/src/containers/WindowViewer.js b/src/containers/WindowViewer.js
index 1a687a388ab22f2532ee074ccdda52bd47893a58..d61c69c21f37a6179a505e954e7607aef4fb909a 100644
--- a/src/containers/WindowViewer.js
+++ b/src/containers/WindowViewer.js
@@ -3,19 +3,13 @@ import { connect } from 'react-redux';
 import { withPlugins } from '../extend/withPlugins';
 import * as actions from '../state/actions';
 import { WindowViewer } from '../components/WindowViewer';
-import { getVisibleCanvases } from '../state/selectors';
 
 /**
  * mapStateToProps - to hook up connect
  * @memberof WindowViewer
  * @private
  */
-const mapStateToProps = (state, { windowId }) => (
-  {
-    currentCanvases: getVisibleCanvases(state, { windowId }) || [],
-    infoResponses: state.infoResponses,
-  }
-);
+const mapStateToProps = (state, { windowId }) => ({});
 
 /**
  * mapDispatchToProps - used to hook up connect to action creators
diff --git a/src/state/sagas/windows.js b/src/state/sagas/windows.js
index f25da8822aff67334f43643f6d2f9fc47d1eb7a0..656a9f0947511fbb7a1dc3e69615180e1b57451c 100644
--- a/src/state/sagas/windows.js
+++ b/src/state/sagas/windows.js
@@ -3,6 +3,7 @@ import {
 } from 'redux-saga/effects';
 import ActionTypes from '../actions/action-types';
 import MiradorManifest from '../../lib/MiradorManifest';
+import MiradorCanvas from '../../lib/MiradorCanvas';
 import {
   setContentSearchCurrentAnnotation,
   selectAnnotation,
@@ -11,6 +12,7 @@ import {
   setCanvas,
   fetchSearch,
   receiveManifest,
+  fetchInfoResponse,
 } from '../actions';
 import {
   getSearchForWindow, getSearchAnnotationsForCompanionWindow,
@@ -22,6 +24,8 @@ import {
   getVisibleCanvasIds,
   getWorkspace,
   getElasticLayout,
+  getCanvases,
+  selectInfoResponses,
 } from '../selectors';
 import { fetchManifest } from './iiif';
 
@@ -183,12 +187,28 @@ export function* setCanvasforSelectedAnnotation({ annotationId, windowId }) {
   yield put(thunk);
 }
 
+/** Fetch info responses for the visible canvases */
+export function* fetchInfoResponses({ visibleCanvases: visibleCanvasIds, windowId }) {
+  const canvases = yield select(getCanvases, { windowId });
+  const infoResponses = yield select(selectInfoResponses);
+  const visibleCanvases = (canvases || []).filter(c => visibleCanvasIds.includes(c.id));
+
+  yield all(visibleCanvases.map((canvas) => {
+    const miradorCanvas = new MiradorCanvas(canvas);
+    return all(miradorCanvas.iiifImageResources.map(imageResource => (
+      !infoResponses[imageResource.getServices()[0].id]
+        && put(fetchInfoResponse({ imageResource }))
+    )).filter(Boolean));
+  }));
+}
+
 /** */
 export default function* windowsSaga() {
   yield all([
     takeEvery(ActionTypes.ADD_WINDOW, fetchWindowManifest),
     takeEvery(ActionTypes.UPDATE_WINDOW, fetchWindowManifest),
     takeEvery(ActionTypes.SET_CANVAS, setCurrentAnnotationsOnCurrentCanvas),
+    takeEvery(ActionTypes.SET_CANVAS, fetchInfoResponses),
     takeEvery(ActionTypes.SET_WINDOW_VIEW_TYPE, updateVisibleCanvases),
     takeEvery(ActionTypes.RECEIVE_SEARCH, setCanvasOfFirstSearchResult),
     takeEvery(ActionTypes.SELECT_ANNOTATION, setCanvasforSelectedAnnotation),