Bug 1418274 - Responsive Design Mode to ES6 Classes, prop-types and react-dom-factories r?jryans draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 17 Nov 2017 12:22:29 +0000
changeset 701172 e5f3e2a52d53cb0b81a9fefcfeb3cf4e5f890a2c
parent 700905 081c06e175b2b4431b7af5ea594ff0373e97b70a
child 741116 cb421b805fe85b1cf837eb0a4288a1314a2fe0ff
push id90103
push userbmo:mratcliffe@mozilla.com
push dateTue, 21 Nov 2017 10:15:37 +0000
reviewersjryans
bugs1418274
milestone59.0a1
Bug 1418274 - Responsive Design Mode to ES6 Classes, prop-types and react-dom-factories r?jryans MozReview-Commit-ID: 1QWLZwVlyVQ
devtools/client/responsive.html/app.js
devtools/client/responsive.html/components/Browser.js
devtools/client/responsive.html/components/DeviceAdder.js
devtools/client/responsive.html/components/DeviceModal.js
devtools/client/responsive.html/components/DevicePixelRatioSelector.js
devtools/client/responsive.html/components/DeviceSelector.js
devtools/client/responsive.html/components/GlobalToolbar.js
devtools/client/responsive.html/components/NetworkThrottlingSelector.js
devtools/client/responsive.html/components/ResizableViewport.js
devtools/client/responsive.html/components/Viewport.js
devtools/client/responsive.html/components/ViewportDimension.js
devtools/client/responsive.html/components/ViewportToolbar.js
devtools/client/responsive.html/components/Viewports.js
devtools/client/responsive.html/types.js
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
- /* eslint-env browser */
+/* eslint-env browser */
 
 "use strict";
 
-const { createClass, createFactory, PropTypes, DOM: dom } =
-  require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
   addCustomDevice,
   removeCustomDevice,
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
@@ -27,122 +28,142 @@ const {
   resizeViewport,
   rotateViewport,
 } = require("./actions/viewports");
 const DeviceModal = createFactory(require("./components/DeviceModal"));
 const GlobalToolbar = createFactory(require("./components/GlobalToolbar"));
 const Viewports = createFactory(require("./components/Viewports"));
 const Types = require("./types");
 
-let App = createClass({
-  displayName: "App",
+class App extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      dispatch: PropTypes.func.isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
+      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    dispatch: PropTypes.func.isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
-    viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
+    this.onBrowserMounted = this.onBrowserMounted.bind(this);
+    this.onChangeDevice = this.onChangeDevice.bind(this);
+    this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
+    this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
+    this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
+    this.onContentResize = this.onContentResize.bind(this);
+    this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
+    this.onExit = this.onExit.bind(this);
+    this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
+    this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
+    this.onResizeViewport = this.onResizeViewport.bind(this);
+    this.onRotateViewport = this.onRotateViewport.bind(this);
+    this.onScreenshot = this.onScreenshot.bind(this);
+    this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
+    this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
+  }
 
   onAddCustomDevice(device) {
     this.props.dispatch(addCustomDevice(device));
-  },
+  }
 
   onBrowserMounted() {
     window.postMessage({ type: "browser-mounted" }, "*");
-  },
+  }
 
   onChangeDevice(id, device, deviceType) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
     // message is sent from the action creator and device property changes are sent from
     // there instead of this function.
     window.postMessage({
       type: "change-device",
       device,
     }, "*");
     this.props.dispatch(changeDevice(id, device.name, deviceType));
     this.props.dispatch(changeTouchSimulation(device.touch));
     this.props.dispatch(changePixelRatio(id, device.pixelRatio));
-  },
+  }
 
   onChangeNetworkThrottling(enabled, profile) {
     window.postMessage({
       type: "change-network-throtting",
       enabled,
       profile,
     }, "*");
     this.props.dispatch(changeNetworkThrottling(enabled, profile));
-  },
+  }
 
   onChangePixelRatio(pixelRatio) {
     window.postMessage({
       type: "change-pixel-ratio",
       pixelRatio,
     }, "*");
     this.props.dispatch(changePixelRatio(0, pixelRatio));
-  },
+  }
 
   onChangeTouchSimulation(enabled) {
     window.postMessage({
       type: "change-touch-simulation",
       enabled,
     }, "*");
     this.props.dispatch(changeTouchSimulation(enabled));
-  },
+  }
 
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
-  },
+  }
 
   onDeviceListUpdate(devices) {
     updatePreferredDevices(devices);
-  },
+  }
 
   onExit() {
     window.postMessage({ type: "exit" }, "*");
-  },
+  }
 
   onRemoveCustomDevice(device) {
     this.props.dispatch(removeCustomDevice(device));
-  },
+  }
 
   onRemoveDeviceAssociation(id) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that device
     // property changes are sent from there instead of this function.
     this.props.dispatch(removeDeviceAssociation(id));
     this.props.dispatch(changeTouchSimulation(false));
     this.props.dispatch(changePixelRatio(id, 0));
-  },
+  }
 
   onResizeViewport(id, width, height) {
     this.props.dispatch(resizeViewport(id, width, height));
-  },
+  }
 
   onRotateViewport(id) {
     this.props.dispatch(rotateViewport(id));
-  },
+  }
 
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
-  },
+  }
 
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
-  },
+  }
 
   onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
     this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
-  },
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       networkThrottling,
       screenshot,
       touchSimulation,
@@ -216,13 +237,12 @@ let App = createClass({
         devices,
         onAddCustomDevice,
         onDeviceListUpdate,
         onRemoveCustomDevice,
         onUpdateDeviceDisplayed,
         onUpdateDeviceModal,
       })
     );
-  },
-
-});
+  }
+}
 
 module.exports = connect(state => state)(App);
--- a/devtools/client/responsive.html/components/Browser.js
+++ b/devtools/client/responsive.html/components/Browser.js
@@ -2,44 +2,46 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const Services = require("Services");
-const { Task } = require("devtools/shared/task");
 const flags = require("devtools/shared/flags");
-const { DOM: dom, createClass, addons, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const e10s = require("../utils/e10s");
 const message = require("../utils/message");
 const { getToplevelWindow } = require("../utils/window");
 
 const FRAME_SCRIPT = "resource://devtools/client/responsive.html/browser/content.js";
 
-module.exports = createClass({
-
+class Browser extends PureComponent {
   /**
    * This component is not allowed to depend directly on frequently changing
    * data (width, height) due to the use of `dangerouslySetInnerHTML` below.
    * Any changes in props will cause the <iframe> to be removed and added again,
    * throwing away the current state of the page.
    */
-  displayName: "Browser",
+  static get propTypes() {
+    return {
+      swapAfterMount: PropTypes.bool.isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    swapAfterMount: PropTypes.bool.isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onContentResize = this.onContentResize.bind(this);
+  }
 
   /**
    * Before the browser is mounted, listen for `remote-browser-shown` so that we know when
    * the browser is fully ready.  Without waiting for an event such as this, we don't know
    * whether all frame state for the browser is fully initialized (since some happens
    * async after the element is added), and swapping browsers can fail if this state is
    * not ready.
    */
@@ -50,92 +52,92 @@ module.exports = createClass({
         if (frameLoader.ownerElement != browser) {
           return;
         }
         Services.obs.removeObserver(handler, "remote-browser-shown");
         resolve();
       };
       Services.obs.addObserver(handler, "remote-browser-shown");
     });
-  },
+  }
 
   /**
    * Once the browser element has mounted, load the frame script and enable
    * various features, like floating scrollbars.
    */
-  componentDidMount: Task.async(function* () {
+  async componentDidMount() {
     // If we are not swapping browsers after mount, it's safe to start the frame
     // script now.
     if (!this.props.swapAfterMount) {
-      yield this.startFrameScript();
+      await this.startFrameScript();
     }
 
     // Notify manager.js that this browser has mounted, so that it can trigger
     // a swap if needed and continue with the rest of its startup.
-    yield this.browserShown;
+    await this.browserShown;
     this.props.onBrowserMounted();
 
     // If we are swapping browsers after mount, wait for the swap to complete
     // and start the frame script after that.
     if (this.props.swapAfterMount) {
-      yield message.wait(window, "start-frame-script");
-      yield this.startFrameScript();
+      await message.wait(window, "start-frame-script");
+      await this.startFrameScript();
       message.post(window, "start-frame-script:done");
     }
 
     // Stop the frame script when requested in the future.
     message.wait(window, "stop-frame-script").then(() => {
       this.stopFrameScript();
     });
-  }),
+  }
 
   onContentResize(msg) {
     let { onContentResize } = this.props;
     let { width, height } = msg.data;
     onContentResize({
       width,
       height,
     });
-  },
+  }
 
-  startFrameScript: Task.async(function* () {
+  async startFrameScript() {
     let { onContentResize } = this;
     let browser = this.refs.browserContainer.querySelector("iframe.browser");
     let mm = browser.frameLoader.messageManager;
 
     // Notify tests when the content has received a resize event.  This is not
     // quite the same timing as when we _set_ a new size around the browser,
     // since it still needs to do async work before the content is actually
     // resized to match.
     e10s.on(mm, "OnContentResize", onContentResize);
 
     let ready = e10s.once(mm, "ChildScriptReady");
     mm.loadFrameScript(FRAME_SCRIPT, true);
-    yield ready;
+    await ready;
 
     let browserWindow = getToplevelWindow(window);
     let requiresFloatingScrollbars =
       !browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
 
-    yield e10s.request(mm, "Start", {
+    await e10s.request(mm, "Start", {
       requiresFloatingScrollbars,
       // Tests expect events on resize to yield on various size changes
       notifyOnResize: flags.testing,
     });
-  }),
+  }
 
-  stopFrameScript: Task.async(function* () {
+  async stopFrameScript() {
     let { onContentResize } = this;
     let browser = this.refs.browserContainer.querySelector("iframe.browser");
     let mm = browser.frameLoader.messageManager;
 
     e10s.off(mm, "OnContentResize", onContentResize);
-    yield e10s.request(mm, "Stop");
+    await e10s.request(mm, "Stop");
     message.post(window, "stop-frame-script:done");
-  }),
+  }
 
   render() {
     return dom.div(
       {
         ref: "browserContainer",
         className: "browser-container",
 
         /**
@@ -155,11 +157,12 @@ module.exports = createClass({
           __html: `<iframe class="browser" mozbrowser="true"
                            remote="true" remoteType="web"
                            noisolation="true" allowfullscreen="true"
                            src="about:blank" width="100%" height="100%">
                    </iframe>`
         }
       }
     );
-  },
+  }
+}
 
-});
+module.exports = Browser;
--- a/devtools/client/responsive.html/components/DeviceAdder.js
+++ b/devtools/client/responsive.html/components/DeviceAdder.js
@@ -1,62 +1,65 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { getFormatStr, getStr } = require("../utils/l10n");
 const Types = require("../types");
 const ViewportDimension = createFactory(require("./ViewportDimension.js"));
 
-module.exports = createClass({
-  displayName: "DeviceAdder",
+class DeviceAdder extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+      onAddCustomDevice: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    viewportTemplate: PropTypes.shape(Types.viewport).isRequired,
-    onAddCustomDevice: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {};
-  },
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this.onChangeSize = this.onChangeSize.bind(this);
+    this.onDeviceAdderShow = this.onDeviceAdderShow.bind(this);
+    this.onDeviceAdderSave = this.onDeviceAdderSave.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let {
       width,
       height,
     } = nextProps.viewportTemplate;
 
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   onChangeSize(width, height) {
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   onDeviceAdderShow() {
     this.setState({
       deviceAdderDisplayed: true,
     });
-  },
+  }
 
   onDeviceAdderSave() {
     let {
       devices,
       onAddCustomDevice,
     } = this.props;
 
     if (!this.pixelRatioInput.checkValidity()) {
@@ -73,17 +76,17 @@ module.exports = createClass({
     onAddCustomDevice({
       name: this.nameInput.value,
       width: this.state.width,
       height: this.state.height,
       pixelRatio: parseFloat(this.pixelRatioInput.value),
       userAgent: this.userAgentInput.value,
       touch: this.touchInput.checked,
     });
-  },
+  }
 
   render() {
     let {
       devices,
       viewportTemplate,
     } = this.props;
 
     let {
@@ -243,10 +246,12 @@ module.exports = createClass({
       dom.button(
         {
           id: "device-adder-save",
           onClick: this.onDeviceAdderSave,
         },
         getStr("responsive.deviceAdderSave")
       )
     );
-  },
-});
+  }
+}
+
+module.exports = DeviceAdder;
--- a/devtools/client/responsive.html/components/DeviceModal.js
+++ b/devtools/client/responsive.html/components/DeviceModal.js
@@ -1,45 +1,49 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { getStr, getFormatStr } = require("../utils/l10n");
 const Types = require("../types");
 const DeviceAdder = createFactory(require("./DeviceAdder"));
 
-module.exports = createClass({
-  displayName: "DeviceModal",
+class DeviceModal extends PureComponent {
+  static get propTypes() {
+    return {
+      deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
+      devices: PropTypes.shape(Types.devices).isRequired,
+      onAddCustomDevice: PropTypes.func.isRequired,
+      onDeviceListUpdate: PropTypes.func.isRequired,
+      onRemoveCustomDevice: PropTypes.func.isRequired,
+      onUpdateDeviceDisplayed: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    deviceAdderViewportTemplate: PropTypes.shape(Types.viewport).isRequired,
-    devices: PropTypes.shape(Types.devices).isRequired,
-    onAddCustomDevice: PropTypes.func.isRequired,
-    onDeviceListUpdate: PropTypes.func.isRequired,
-    onRemoveCustomDevice: PropTypes.func.isRequired,
-    onUpdateDeviceDisplayed: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {};
-  },
+  constructor(props) {
+    super(props);
+    this.state = {};
+    this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
+    this.onDeviceCheckboxChange = this.onDeviceCheckboxChange.bind(this);
+    this.onDeviceModalSubmit = this.onDeviceModalSubmit.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     window.addEventListener("keydown", this.onKeyDown, true);
-  },
+  }
 
   componentWillReceiveProps(nextProps) {
     let {
       devices: oldDevices,
     } = this.props;
     let {
       devices,
     } = nextProps;
@@ -49,38 +53,38 @@ module.exports = createClass({
       for (let type of devices.types) {
         for (let device of devices[type]) {
           this.setState({
             [device.name]: device.displayed,
           });
         }
       }
     }
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("keydown", this.onKeyDown, true);
-  },
+  }
 
   onAddCustomDevice(device) {
     this.props.onAddCustomDevice(device);
     // Default custom devices to enabled
     this.setState({
       [device.name]: true,
     });
-  },
+  }
 
   onDeviceCheckboxChange({ nativeEvent: { button }, target }) {
     if (button !== 0) {
       return;
     }
     this.setState({
       [target.value]: !this.state[target.value]
     });
-  },
+  }
 
   onDeviceModalSubmit() {
     let {
       devices,
       onDeviceListUpdate,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModal,
     } = this.props;
@@ -103,30 +107,30 @@ module.exports = createClass({
         if (this.state[device.name] != device.displayed) {
           onUpdateDeviceDisplayed(device, type, this.state[device.name]);
         }
       }
     }
 
     onDeviceListUpdate(preferredDevices);
     onUpdateDeviceModal(false);
-  },
+  }
 
   onKeyDown(event) {
     if (!this.props.devices.isModalOpen) {
       return;
     }
     // Escape keycode
     if (event.keyCode === 27) {
       let {
         onUpdateDeviceModal
       } = this.props;
       onUpdateDeviceModal(false);
     }
-  },
+  }
 
   render() {
     let {
       deviceAdderViewportTemplate,
       devices,
       onRemoveCustomDevice,
       onUpdateDeviceModal,
     } = this.props;
@@ -225,10 +229,12 @@ module.exports = createClass({
       ),
       dom.div(
         {
           className: "modal-overlay",
           onClick: () => onUpdateDeviceModal(false),
         }
       )
     );
-  },
-});
+  }
+}
+
+module.exports = DeviceModal;
--- a/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
+++ b/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env browser */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent} = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const Types = require("../types");
 const { getStr, getFormatStr } = require("../utils/l10n");
 const labelForOption = value => getFormatStr("responsive.devicePixelRatioOption", value);
 
 const PIXEL_RATIO_PRESET = [1, 2, 3];
 
 const createVisibleOption = value => {
@@ -29,44 +30,47 @@ const createHiddenOption = value => {
   return dom.option({
     value,
     title: label,
     hidden: true,
     disabled: true,
   }, label);
 };
 
-module.exports = createClass({
-  displayName: "DevicePixelRatioSelector",
+class DevicePixelRatioSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
+      onChangePixelRatio: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-    onChangePixelRatio: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  mixins: [ addons.PureRenderMixin ],
-
-  getInitialState() {
-    return {
+    this.state = {
       isFocused: false
     };
-  },
+
+    this.onFocusChange = this.onFocusChange.bind(this);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onFocusChange({type}) {
     this.setState({
       isFocused: type === "focus"
     });
-  },
+  }
 
   onSelectChange({ target }) {
     this.props.onChangePixelRatio(+target.value);
-  },
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       selectedDevice,
       selectedPixelRatio,
     } = this.props;
@@ -121,11 +125,12 @@ module.exports = createClass({
         onChange: this.onSelectChange,
         onFocus: this.onFocusChange,
         onBlur: this.onFocusChange,
         className: selectorClass,
         title: title
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = DevicePixelRatioSelector;
--- a/devtools/client/responsive.html/components/DeviceSelector.js
+++ b/devtools/client/responsive.html/components/DeviceSelector.js
@@ -1,34 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { getStr } = require("../utils/l10n");
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const OPEN_DEVICE_MODAL_VALUE = "OPEN_DEVICE_MODAL";
 
-module.exports = createClass({
-  displayName: "DeviceSelector",
+class DeviceSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      viewportId: PropTypes.number.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    viewportId: PropTypes.number.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onSelectChange({ target }) {
     let {
       devices,
       viewportId,
       onChangeDevice,
       onResizeViewport,
       onUpdateDeviceModal,
@@ -42,17 +46,17 @@ module.exports = createClass({
       for (let device of devices[type]) {
         if (device.name === target.value) {
           onResizeViewport(device.width, device.height);
           onChangeDevice(device, type);
           return;
         }
       }
     }
-  },
+  }
 
   render() {
     let {
       devices,
       selectedDevice,
     } = this.props;
 
     let options = [];
@@ -116,11 +120,12 @@ module.exports = createClass({
         className: selectClass,
         value: selectedDevice,
         title: selectedDevice,
         onChange: this.onSelectChange,
         disabled: (state !== Types.deviceListState.LOADED),
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = DeviceSelector;
--- a/devtools/client/responsive.html/components/GlobalToolbar.js
+++ b/devtools/client/responsive.html/components/GlobalToolbar.js
@@ -1,41 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 const DevicePixelRatioSelector = createFactory(require("./DevicePixelRatioSelector"));
 const NetworkThrottlingSelector = createFactory(require("./NetworkThrottlingSelector"));
 
-module.exports = createClass({
-  displayName: "GlobalToolbar",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    displayPixelRatio: Types.pixelRatio.value.isRequired,
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    selectedDevice: PropTypes.string.isRequired,
-    selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
-    touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
-    onChangeNetworkThrottling: PropTypes.func.isRequired,
-    onChangePixelRatio: PropTypes.func.isRequired,
-    onChangeTouchSimulation: PropTypes.func.isRequired,
-    onExit: PropTypes.func.isRequired,
-    onScreenshot: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+class GlobalToolbar extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      displayPixelRatio: Types.pixelRatio.value.isRequired,
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      selectedDevice: PropTypes.string.isRequired,
+      selectedPixelRatio: PropTypes.shape(Types.pixelRatio).isRequired,
+      touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
+      onChangeNetworkThrottling: PropTypes.func.isRequired,
+      onChangePixelRatio: PropTypes.func.isRequired,
+      onChangeTouchSimulation: PropTypes.func.isRequired,
+      onExit: PropTypes.func.isRequired,
+      onScreenshot: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       displayPixelRatio,
       networkThrottling,
       screenshot,
       selectedDevice,
@@ -91,11 +90,12 @@ module.exports = createClass({
       }),
       dom.button({
         id: "global-exit-button",
         className: "toolbar-button devtools-button",
         title: getStr("responsive.exit"),
         onClick: onExit,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = GlobalToolbar;
--- a/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
+++ b/devtools/client/responsive.html/components/NetworkThrottlingSelector.js
@@ -1,31 +1,34 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 const throttlingProfiles = require("devtools/client/shared/network-throttling-profiles");
 
-module.exports = createClass({
-
-  displayName: "NetworkThrottlingSelector",
+class NetworkThrottlingSelector extends PureComponent {
+  static get propTypes() {
+    return {
+      networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
+      onChangeNetworkThrottling: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
-    onChangeNetworkThrottling: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+  constructor(props) {
+    super(props);
+    this.onSelectChange = this.onSelectChange.bind(this);
+  }
 
   onSelectChange({ target }) {
     let {
       onChangeNetworkThrottling,
     } = this.props;
 
     if (target.value == getStr("responsive.noThrottling")) {
       onChangeNetworkThrottling(false, "");
@@ -33,17 +36,17 @@ module.exports = createClass({
     }
 
     for (let profile of throttlingProfiles) {
       if (profile.id === target.value) {
         onChangeNetworkThrottling(true, profile.id);
         return;
       }
     }
-  },
+  }
 
   render() {
     let {
       networkThrottling,
     } = this.props;
 
     let selectClass = "";
     let selectedProfile;
@@ -82,11 +85,12 @@ module.exports = createClass({
       {
         id: "global-network-throttling-selector",
         className: selectClass,
         value: selectedProfile,
         onChange: this.onSelectChange,
       },
       ...listContent
     );
-  },
+  }
+}
 
-});
+module.exports = NetworkThrottlingSelector;
--- a/devtools/client/responsive.html/components/ResizableViewport.js
+++ b/devtools/client/responsive.html/components/ResizableViewport.js
@@ -1,80 +1,86 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global window */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Constants = require("../constants");
 const Types = require("../types");
 const Browser = createFactory(require("./Browser"));
 const ViewportToolbar = createFactory(require("./ViewportToolbar"));
 
 const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION;
 const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION;
 
-module.exports = createClass({
-
-  displayName: "ResizableViewport",
+class ResizableViewport extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      swapAfterMount: PropTypes.bool.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    swapAfterMount: PropTypes.bool.isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       isResizing: false,
       lastClientX: 0,
       lastClientY: 0,
       ignoreX: false,
       ignoreY: false,
     };
-  },
+
+    this.onResizeStart = this.onResizeStart.bind(this);
+    this.onResizeStop = this.onResizeStop.bind(this);
+    this.onResizeDrag = this.onResizeDrag.bind(this);
+  }
 
   onResizeStart({ target, clientX, clientY }) {
     window.addEventListener("mousemove", this.onResizeDrag, true);
     window.addEventListener("mouseup", this.onResizeStop, true);
 
     this.setState({
       isResizing: true,
       lastClientX: clientX,
       lastClientY: clientY,
       ignoreX: target === this.refs.resizeBarY,
       ignoreY: target === this.refs.resizeBarX,
     });
-  },
+  }
 
   onResizeStop() {
     window.removeEventListener("mousemove", this.onResizeDrag, true);
     window.removeEventListener("mouseup", this.onResizeStop, true);
 
     this.setState({
       isResizing: false,
       lastClientX: 0,
       lastClientY: 0,
       ignoreX: false,
       ignoreY: false,
     });
-  },
+  }
 
   onResizeDrag({ clientX, clientY }) {
     if (!this.state.isResizing) {
       return;
     }
 
     let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state;
     // the viewport is centered horizontally, so horizontal resize resizes
@@ -115,17 +121,17 @@ module.exports = createClass({
       // UA, so it's important to keep doing this for now.
       this.props.onRemoveDeviceAssociation();
     }
 
     this.setState({
       lastClientX,
       lastClientY
     });
-  },
+  }
 
   render() {
     let {
       devices,
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
@@ -182,11 +188,12 @@ module.exports = createClass({
         onMouseDown: this.onResizeStart,
       }),
       dom.div({
         ref: "resizeBarY",
         className: "viewport-vertical-resize-handle",
         onMouseDown: this.onResizeStart,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = ResizableViewport;
--- a/devtools/client/responsive.html/components/Viewport.js
+++ b/devtools/client/responsive.html/components/Viewport.js
@@ -1,74 +1,82 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const ResizableViewport = createFactory(require("./ResizableViewport"));
 const ViewportDimension = createFactory(require("./ViewportDimension"));
 
-module.exports = createClass({
-
-  displayName: "Viewport",
+class Viewport extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      swapAfterMount: PropTypes.bool.isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    swapAfterMount: PropTypes.bool.isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    this.onChangeDevice = this.onChangeDevice.bind(this);
+    this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
+    this.onResizeViewport = this.onResizeViewport.bind(this);
+    this.onRotateViewport = this.onRotateViewport.bind(this);
+  }
 
   onChangeDevice(device, deviceType) {
     let {
       viewport,
       onChangeDevice,
     } = this.props;
 
     onChangeDevice(viewport.id, device, deviceType);
-  },
+  }
 
   onRemoveDeviceAssociation() {
     let {
       viewport,
       onRemoveDeviceAssociation,
     } = this.props;
 
     onRemoveDeviceAssociation(viewport.id);
-  },
+  }
 
   onResizeViewport(width, height) {
     let {
       viewport,
       onResizeViewport,
     } = this.props;
 
     onResizeViewport(viewport.id, width, height);
-  },
+  }
 
   onRotateViewport() {
     let {
       viewport,
       onRotateViewport,
     } = this.props;
 
     onRotateViewport(viewport.id);
-  },
+  }
 
   render() {
     let {
       devices,
       screenshot,
       swapAfterMount,
       viewport,
       onBrowserMounted,
@@ -101,11 +109,12 @@ module.exports = createClass({
         onChangeDevice,
         onContentResize,
         onRemoveDeviceAssociation,
         onResizeViewport,
         onRotateViewport,
         onUpdateDeviceModal,
       })
     );
-  },
+  }
+}
 
-});
+module.exports = Viewport;
--- a/devtools/client/responsive.html/components/ViewportDimension.js
+++ b/devtools/client/responsive.html/components/ViewportDimension.js
@@ -1,109 +1,118 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Constants = require("../constants");
 const Types = require("../types");
 
-module.exports = createClass({
-  displayName: "ViewportDimension",
+class ViewportDimension extends Component {
+  static get propTypes() {
+    return {
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onChangeSize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onChangeSize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
+    let { width, height } = props.viewport;
 
-  getInitialState() {
-    let { width, height } = this.props.viewport;
-
-    return {
+    this.state = {
       width,
       height,
       isEditing: false,
       isInvalid: false,
     };
-  },
+
+    this.validateInput = this.validateInput.bind(this);
+    this.onInputBlur = this.onInputBlur.bind(this);
+    this.onInputChange = this.onInputChange.bind(this);
+    this.onInputFocus = this.onInputFocus.bind(this);
+    this.onInputKeyUp = this.onInputKeyUp.bind(this);
+    this.onInputSubmit = this.onInputSubmit.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let { width, height } = nextProps.viewport;
 
     this.setState({
       width,
       height,
     });
-  },
+  }
 
   validateInput(value) {
     let isInvalid = true;
 
     // Check the value is a number and greater than MIN_VIEWPORT_DIMENSION
     if (/^\d{3,4}$/.test(value) &&
         parseInt(value, 10) >= Constants.MIN_VIEWPORT_DIMENSION) {
       isInvalid = false;
     }
 
     this.setState({
       isInvalid,
     });
-  },
+  }
 
   onInputBlur() {
     let { width, height } = this.props.viewport;
 
     if (this.state.width != width || this.state.height != height) {
       this.onInputSubmit();
     }
 
     this.setState({
       isEditing: false,
       inInvalid: false,
     });
-  },
+  }
 
   onInputChange({ target }) {
     if (target.value.length > 4) {
       return;
     }
 
     if (this.refs.widthInput == target) {
       this.setState({ width: target.value });
       this.validateInput(target.value);
     }
 
     if (this.refs.heightInput == target) {
       this.setState({ height: target.value });
       this.validateInput(target.value);
     }
-  },
+  }
 
   onInputFocus() {
     this.setState({
       isEditing: true,
     });
-  },
+  }
 
   onInputKeyUp({ target, keyCode }) {
     // On Enter, submit the input
     if (keyCode == 13) {
       this.onInputSubmit();
     }
 
     // On Esc, blur the target
     if (keyCode == 27) {
       target.blur();
     }
-  },
+  }
 
   onInputSubmit() {
     if (this.state.isInvalid) {
       let { width, height } = this.props.viewport;
 
       this.setState({
         width,
         height,
@@ -115,17 +124,17 @@ module.exports = createClass({
 
     // Change the device selector back to an unselected device
     // TODO: Bug 1332754: Logic like this probably belongs in the action creator.
     if (this.props.viewport.device) {
       this.props.onRemoveDeviceAssociation();
     }
     this.props.onChangeSize(parseInt(this.state.width, 10),
                             parseInt(this.state.height, 10));
-  },
+  }
 
   render() {
     let editableClass = "viewport-dimension-editable";
     let inputClass = "viewport-dimension-input";
 
     if (this.state.isEditing) {
       editableClass += " editing";
       inputClass += " editing";
@@ -163,11 +172,12 @@ module.exports = createClass({
           value: this.state.height,
           onBlur: this.onInputBlur,
           onChange: this.onInputChange,
           onFocus: this.onInputFocus,
           onKeyUp: this.onInputKeyUp,
         })
       )
     );
-  },
+  }
+}
 
-});
+module.exports = ViewportDimension;
--- a/devtools/client/responsive.html/components/ViewportToolbar.js
+++ b/devtools/client/responsive.html/components/ViewportToolbar.js
@@ -1,34 +1,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes, addons } =
-  require("devtools/client/shared/vendor/react");
+const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 const DeviceSelector = createFactory(require("./DeviceSelector"));
 
-module.exports = createClass({
-  displayName: "ViewportToolbar",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    viewport: PropTypes.shape(Types.viewport).isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
-
-  mixins: [ addons.PureRenderMixin ],
+class ViewportToolbar extends PureComponent {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      viewport: PropTypes.shape(Types.viewport).isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       viewport,
       onChangeDevice,
       onResizeViewport,
       onRotateViewport,
@@ -48,11 +47,12 @@ module.exports = createClass({
         onUpdateDeviceModal,
       }),
       dom.button({
         className: "viewport-rotate-button toolbar-button devtools-button",
         onClick: onRotateViewport,
         title: getStr("responsive.rotate"),
       })
     );
-  },
+  }
+}
 
-});
+module.exports = ViewportToolbar;
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -1,36 +1,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { DOM: dom, createClass, createFactory, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
 const Types = require("../types");
 const Viewport = createFactory(require("./Viewport"));
 
-module.exports = createClass({
-
-  displayName: "Viewports",
-
-  propTypes: {
-    devices: PropTypes.shape(Types.devices).isRequired,
-    screenshot: PropTypes.shape(Types.screenshot).isRequired,
-    viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
-    onBrowserMounted: PropTypes.func.isRequired,
-    onChangeDevice: PropTypes.func.isRequired,
-    onContentResize: PropTypes.func.isRequired,
-    onRemoveDeviceAssociation: PropTypes.func.isRequired,
-    onResizeViewport: PropTypes.func.isRequired,
-    onRotateViewport: PropTypes.func.isRequired,
-    onUpdateDeviceModal: PropTypes.func.isRequired,
-  },
+class Viewports extends Component {
+  static get propTypes() {
+    return {
+      devices: PropTypes.shape(Types.devices).isRequired,
+      screenshot: PropTypes.shape(Types.screenshot).isRequired,
+      viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
+      onBrowserMounted: PropTypes.func.isRequired,
+      onChangeDevice: PropTypes.func.isRequired,
+      onContentResize: PropTypes.func.isRequired,
+      onRemoveDeviceAssociation: PropTypes.func.isRequired,
+      onResizeViewport: PropTypes.func.isRequired,
+      onRotateViewport: PropTypes.func.isRequired,
+      onUpdateDeviceModal: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     let {
       devices,
       screenshot,
       viewports,
       onBrowserMounted,
       onChangeDevice,
@@ -57,11 +57,12 @@ module.exports = createClass({
           onContentResize,
           onRemoveDeviceAssociation,
           onResizeViewport,
           onRotateViewport,
           onUpdateDeviceModal,
         });
       })
     );
-  },
+  }
+}
 
-});
+module.exports = Viewports;
--- a/devtools/client/responsive.html/types.js
+++ b/devtools/client/responsive.html/types.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { PropTypes } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { createEnum } = require("devtools/client/shared/enum");
 
 // React PropTypes are used to describe the expected "shape" of various common
 // objects that get passed down as props to components.
 
 /* GLOBAL */
 
 /**