Bug 1412311 - DevTools Shared Components to ES6 classes r=nchevobbe
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 27 Oct 2017 15:33:10 +0100
changeset 440593 3fa859a78370f2df0639a63ba7accad45ff7f60a
parent 440562 083a5838f76a418779c2f4fc01152bc3be355fc0
child 440594 be60c2b625ae9a7c5d8711a70ac8722d936b1f27
push id8118
push userryanvm@gmail.com
push dateFri, 03 Nov 2017 00:38:34 +0000
treeherdermozilla-beta@1c336e874ae8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1412311, 1413167
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1412311 - DevTools Shared Components to ES6 classes r=nchevobbe In devtools/client/shared/components/tree/TreeView.js I have had to leave defaultProps outside the getter as a temporary workaround for bug 1413167. MozReview-Commit-ID: 1yaxqFnC92p
devtools/client/shared/components/AutoCompletePopup.js
devtools/client/shared/components/Frame.js
devtools/client/shared/components/HSplitBox.js
devtools/client/shared/components/NotificationBox.js
devtools/client/shared/components/SearchBox.js
devtools/client/shared/components/SidebarToggle.js
devtools/client/shared/components/StackTrace.js
devtools/client/shared/components/Tree.js
devtools/client/shared/components/splitter/Draggable.js
devtools/client/shared/components/splitter/SplitBox.js
devtools/client/shared/components/tabs/TabBar.js
devtools/client/shared/components/tabs/Tabs.js
devtools/client/shared/components/test/mochitest/test_tabs_menu.html
devtools/client/shared/components/tree/LabelCell.js
devtools/client/shared/components/tree/TreeCell.js
devtools/client/shared/components/tree/TreeHeader.js
devtools/client/shared/components/tree/TreeRow.js
devtools/client/shared/components/tree/TreeView.js
--- a/devtools/client/shared/components/AutoCompletePopup.js
+++ b/devtools/client/shared/components/AutoCompletePopup.js
@@ -1,72 +1,79 @@
 /* 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");
-
-module.exports = createClass({
-  displayName: "AutocompletePopup",
+const { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 
-  propTypes: {
-    /**
-     * autocompleteProvider takes search-box's entire input text as `filter` argument
-     * ie. "is:cached pr"
-     * returned value is array of objects like below
-     * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
-     * `value` is used to update the search-box input box for given item
-     * `displayValue` is used to render the autocomplete list
-     */
-    autocompleteProvider: PropTypes.func.isRequired,
-    filter: PropTypes.string.isRequired,
-    onItemSelected: PropTypes.func.isRequired,
-  },
+class AutocompletePopup extends Component {
+  static get propTypes() {
+    return {
+      /**
+       * autocompleteProvider takes search-box's entire input text as `filter` argument
+       * ie. "is:cached pr"
+       * returned value is array of objects like below
+       * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]]
+       * `value` is used to update the search-box input box for given item
+       * `displayValue` is used to render the autocomplete list
+       */
+      autocompleteProvider: PropTypes.func.isRequired,
+      filter: PropTypes.string.isRequired,
+      onItemSelected: PropTypes.func.isRequired,
+    };
+  }
 
-  getInitialState() {
-    return this.computeState(this.props);
-  },
+  constructor(props, context) {
+    super(props, context);
+    this.state = this.computeState(props);
+    this.computeState = this.computeState.bind(this);
+    this.jumpToTop = this.jumpToTop.bind(this);
+    this.jumpToBottom = this.jumpToBottom.bind(this);
+    this.jumpBy = this.jumpBy.bind(this);
+    this.select = this.select.bind(this);
+    this.onMouseDown = this.onMouseDown.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     if (this.props.filter === nextProps.filter) {
       return;
     }
     this.setState(this.computeState(nextProps));
-  },
+  }
 
   componentDidUpdate() {
     if (this.refs.selected) {
       this.refs.selected.scrollIntoView(false);
     }
-  },
+  }
 
   computeState({ autocompleteProvider, filter }) {
     let list = autocompleteProvider(filter);
     let selectedIndex = list.length == 1 ? 0 : -1;
 
     return { list, selectedIndex };
-  },
+  }
 
   /**
    * Use this method to select the top-most item
    * This method is public, called outside of the autocomplete-popup component.
    */
   jumpToTop() {
     this.setState({ selectedIndex: 0 });
-  },
+  }
 
   /**
    * Use this method to select the bottom-most item
    * This method is public.
    */
   jumpToBottom() {
     this.setState({ selectedIndex: this.state.list.length - 1 });
-  },
+  }
 
   /**
    * Increment the selected index with the provided increment value. Will cycle to the
    * beginning/end of the list if the index exceeds the list boundaries.
    * This method is public.
    *
    * @param {number} increment - No. of hops in the direction
    */
@@ -76,32 +83,32 @@ module.exports = createClass({
     if (increment > 0) {
       // Positive cycling
       nextIndex = nextIndex > list.length - 1 ? 0 : nextIndex;
     } else if (increment < 0) {
       // Inverse cycling
       nextIndex = nextIndex < 0 ? list.length - 1 : nextIndex;
     }
     this.setState({selectedIndex: nextIndex});
-  },
+  }
 
   /**
    * Submit the currently selected item to the onItemSelected callback
    * This method is public.
    */
   select() {
     if (this.refs.selected) {
       this.props.onItemSelected(this.refs.selected.dataset.value);
     }
-  },
+  }
 
   onMouseDown(e) {
     e.preventDefault();
     this.setState({ selectedIndex: Number(e.target.dataset.index) }, this.select);
-  },
+  }
 
   render() {
     let { list } = this.state;
 
     return list.length > 0 && dom.div(
       { className: "devtools-autocomplete-popup devtools-monospace" },
       dom.ul(
         { className: "devtools-autocomplete-listbox" },
@@ -119,9 +126,11 @@ module.exports = createClass({
             className: itemClassList.join(" "),
             ref: isSelected ? "selected" : null,
             onMouseDown: this.onMouseDown,
           }, item.displayValue);
         })
       )
     );
   }
-});
+}
+
+module.exports = AutocompletePopup;
--- a/devtools/client/shared/components/Frame.js
+++ b/devtools/client/shared/components/Frame.js
@@ -1,106 +1,112 @@
 /* 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 { DOM: dom, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 const { getSourceNames, parseURL,
         isScratchpadScheme, getSourceMappedFile } = require("devtools/client/shared/source-utils");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
 const webl10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
 
-module.exports = createClass({
-  displayName: "Frame",
+class Frame extends Component {
+  static get propTypes() {
+    return {
+      // SavedFrame, or an object containing all the required properties.
+      frame: PropTypes.shape({
+        functionDisplayName: PropTypes.string,
+        source: PropTypes.string.isRequired,
+        line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
+        column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
+      }).isRequired,
+      // Clicking on the frame link -- probably should link to the debugger.
+      onClick: PropTypes.func.isRequired,
+      // Option to display a function name before the source link.
+      showFunctionName: PropTypes.bool,
+      // Option to display a function name even if it's anonymous.
+      showAnonymousFunctionName: PropTypes.bool,
+      // Option to display a host name after the source link.
+      showHost: PropTypes.bool,
+      // Option to display a host name if the filename is empty or just '/'
+      showEmptyPathAsHost: PropTypes.bool,
+      // Option to display a full source instead of just the filename.
+      showFullSourceUrl: PropTypes.bool,
+      // Service to enable the source map feature for console.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
-  propTypes: {
-    // SavedFrame, or an object containing all the required properties.
-    frame: PropTypes.shape({
-      functionDisplayName: PropTypes.string,
-      source: PropTypes.string.isRequired,
-      line: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
-      column: PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
-    }).isRequired,
-    // Clicking on the frame link -- probably should link to the debugger.
-    onClick: PropTypes.func.isRequired,
-    // Option to display a function name before the source link.
-    showFunctionName: PropTypes.bool,
-    // Option to display a function name even if it's anonymous.
-    showAnonymousFunctionName: PropTypes.bool,
-    // Option to display a host name after the source link.
-    showHost: PropTypes.bool,
-    // Option to display a host name if the filename is empty or just '/'
-    showEmptyPathAsHost: PropTypes.bool,
-    // Option to display a full source instead of just the filename.
-    showFullSourceUrl: PropTypes.bool,
-    // Service to enable the source map feature for console.
-    sourceMapService: PropTypes.object,
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       showFunctionName: false,
       showAnonymousFunctionName: false,
       showHost: false,
       showEmptyPathAsHost: false,
       showFullSourceUrl: false,
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
+    this._locationChanged = this._locationChanged.bind(this);
+    this.getSourceForClick = this.getSourceForClick.bind(this);
+  }
 
   componentWillMount() {
     if (this.props.sourceMapService) {
       const { source, line, column } = this.props.frame;
       this.props.sourceMapService.subscribe(source, line, column,
                                             this._locationChanged);
     }
-  },
+  }
 
   componentWillUnmount() {
     if (this.props.sourceMapService) {
       const { source, line, column } = this.props.frame;
       this.props.sourceMapService.unsubscribe(source, line, column,
                                               this._locationChanged);
     }
-  },
+  }
 
   _locationChanged(isSourceMapped, url, line, column) {
     let newState = {
       isSourceMapped,
     };
     if (isSourceMapped) {
       newState.frame = {
         source: url,
         line,
         column,
         functionDisplayName: this.props.frame.functionDisplayName,
       };
     }
 
     this.setState(newState);
-  },
+  }
 
   /**
    * Utility method to convert the Frame object model to the
    * object model required by the onClick callback.
    * @param Frame frame
    * @returns {{url: *, line: *, column: *, functionDisplayName: *}}
    */
   getSourceForClick(frame) {
     const { source, line, column } = frame;
     return {
       url: source,
       line,
       column,
       functionDisplayName: this.props.frame.functionDisplayName,
     };
-  },
+  }
 
   render() {
     let frame, isSourceMapped;
     let {
       onClick,
       showFunctionName,
       showAnonymousFunctionName,
       showHost,
@@ -230,9 +236,11 @@ module.exports = createClass({
       elements.push(dom.span({
         key: "host",
         className: "frame-link-host",
       }, host));
     }
 
     return dom.span(attributes, ...elements);
   }
-});
+}
+
+module.exports = Frame;
--- a/devtools/client/shared/components/HSplitBox.js
+++ b/devtools/client/shared/components/HSplitBox.js
@@ -20,105 +20,111 @@
 //     |                       e                     |
 //     |                       r                     |
 //     |                       |                     |
 //     |                       |                     |
 //     +-----------------------+---------------------+
 
 const {
   DOM: dom,
-  createClass,
+  Component,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { assert } = require("devtools/shared/DevToolsUtils");
 
-module.exports = createClass({
-  displayName: "HSplitBox",
+class HSplitBox extends Component {
+  static get propTypes() {
+    return {
+      // The contents of the start pane.
+      start: PropTypes.any.isRequired,
 
-  propTypes: {
-    // The contents of the start pane.
-    start: PropTypes.any.isRequired,
-
-    // The contents of the end pane.
-    end: PropTypes.any.isRequired,
+      // The contents of the end pane.
+      end: PropTypes.any.isRequired,
 
-    // The relative width of the start pane, expressed as a number between 0 and
-    // 1. The relative width of the end pane is 1 - startWidth. For example,
-    // with startWidth = .5, both panes are of equal width; with startWidth =
-    // .25, the start panel will take up 1/4 width and the end panel will take
-    // up 3/4 width.
-    startWidth: PropTypes.number,
+      // The relative width of the start pane, expressed as a number between 0 and
+      // 1. The relative width of the end pane is 1 - startWidth. For example,
+      // with startWidth = .5, both panes are of equal width; with startWidth =
+      // .25, the start panel will take up 1/4 width and the end panel will take
+      // up 3/4 width.
+      startWidth: PropTypes.number,
 
-    // A minimum css width value for the start and end panes.
-    minStartWidth: PropTypes.any,
-    minEndWidth: PropTypes.any,
+      // A minimum css width value for the start and end panes.
+      minStartWidth: PropTypes.any,
+      minEndWidth: PropTypes.any,
 
-    // A callback fired when the user drags the splitter to resize the relative
-    // pane widths. The function is passed the startWidth value that would put
-    // the splitter underneath the users mouse.
-    onResize: PropTypes.func.isRequired,
-  },
+      // A callback fired when the user drags the splitter to resize the relative
+      // pane widths. The function is passed the startWidth value that would put
+      // the splitter underneath the users mouse.
+      onResize: PropTypes.func.isRequired,
+    };
+  }
 
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       startWidth: 0.5,
       minStartWidth: "20px",
       minEndWidth: "20px",
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       mouseDown: false
     };
-  },
+
+    this._onMouseDown = this._onMouseDown.bind(this);
+    this._onMouseUp = this._onMouseUp.bind(this);
+    this._onMouseMove = this._onMouseMove.bind(this);
+  }
 
   componentDidMount() {
     document.defaultView.top.addEventListener("mouseup", this._onMouseUp);
     document.defaultView.top.addEventListener("mousemove", this._onMouseMove);
-  },
+  }
 
   componentWillUnmount() {
     document.defaultView.top.removeEventListener("mouseup", this._onMouseUp);
     document.defaultView.top.removeEventListener("mousemove", this._onMouseMove);
-  },
+  }
 
   _onMouseDown(event) {
     if (event.button !== 0) {
       return;
     }
 
     this.setState({ mouseDown: true });
     event.preventDefault();
-  },
+  }
 
   _onMouseUp(event) {
     if (event.button !== 0 || !this.state.mouseDown) {
       return;
     }
 
     this.setState({ mouseDown: false });
     event.preventDefault();
-  },
+  }
 
   _onMouseMove(event) {
     if (!this.state.mouseDown) {
       return;
     }
 
     const rect = this.refs.box.getBoundingClientRect();
     const { left, right } = rect;
     const width = right - left;
     const direction = this.refs.box.ownerDocument.dir;
     const relative = direction == "rtl" ? right - event.clientX
                                         : event.clientX - left;
     this.props.onResize(relative / width);
 
     event.preventDefault();
-  },
+  }
 
   render() {
     /* eslint-disable no-shadow */
     const { start, end, startWidth, minStartWidth, minEndWidth } = this.props;
     assert(startWidth => 0 && startWidth <= 1,
            "0 <= this.props.startWidth <= 1");
     /* eslint-enable */
     return dom.div(
@@ -144,9 +150,11 @@ module.exports = createClass({
         {
           className: "h-split-box-pane",
           style: { flex: 1 - startWidth, minWidth: minEndWidth },
         },
         end
       )
     );
   }
-});
+}
+
+module.exports = HSplitBox;
--- a/devtools/client/shared/components/NotificationBox.js
+++ b/devtools/client/shared/components/NotificationBox.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 const Immutable = require("devtools/client/shared/vendor/immutable");
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
 
 // Shortcuts
-const { PropTypes, createClass, DOM } = React;
+const { PropTypes, Component, DOM } = React;
 const { div, span, button } = DOM;
 
 // Priority Levels
 const PriorityLevels = {
   PRIORITY_INFO_LOW: 1,
   PRIORITY_INFO_MEDIUM: 2,
   PRIORITY_INFO_HIGH: 3,
   PRIORITY_WARNING_LOW: 4,
@@ -29,82 +29,91 @@ const PriorityLevels = {
 
 /**
  * This component represents Notification Box - HTML alternative for
  * <xul:notificationbox> binding.
  *
  * See also MDN for more info about <xul:notificationbox>:
  * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox
  */
-var NotificationBox = createClass({
-  displayName: "NotificationBox",
-
-  propTypes: {
-    // List of notifications appended into the box.
-    notifications: PropTypes.arrayOf(PropTypes.shape({
-      // label to appear on the notification.
-      label: PropTypes.string.isRequired,
-
-      // Value used to identify the notification
-      value: PropTypes.string.isRequired,
-
-      // URL of image to appear on the notification. If "" then an icon
-      // appropriate for the priority level is used.
-      image: PropTypes.string.isRequired,
-
-      // Notification priority; see Priority Levels.
-      priority: PropTypes.number.isRequired,
-
-      // Array of button descriptions to appear on the notification.
-      buttons: PropTypes.arrayOf(PropTypes.shape({
-        // Function to be called when the button is activated.
-        // This function is passed three arguments:
-        // 1) the NotificationBox component the button is associated with
-        // 2) the button description as passed to appendNotification.
-        // 3) the element which was the target of the button press event.
-        // If the return value from this function is not True, then the
-        // notification is closed. The notification is also not closed
-        // if an error is thrown.
-        callback: PropTypes.func.isRequired,
-
-        // The label to appear on the button.
+class NotificationBox extends Component {
+  static get propTypes() {
+    return {
+      // List of notifications appended into the box.
+      notifications: PropTypes.arrayOf(PropTypes.shape({
+        // label to appear on the notification.
         label: PropTypes.string.isRequired,
 
-        // The accesskey attribute set on the <button> element.
-        accesskey: PropTypes.string,
+        // Value used to identify the notification
+        value: PropTypes.string.isRequired,
+
+        // URL of image to appear on the notification. If "" then an icon
+        // appropriate for the priority level is used.
+        image: PropTypes.string.isRequired,
+
+        // Notification priority; see Priority Levels.
+        priority: PropTypes.number.isRequired,
+
+        // Array of button descriptions to appear on the notification.
+        buttons: PropTypes.arrayOf(PropTypes.shape({
+          // Function to be called when the button is activated.
+          // This function is passed three arguments:
+          // 1) the NotificationBox component the button is associated with
+          // 2) the button description as passed to appendNotification.
+          // 3) the element which was the target of the button press event.
+          // If the return value from this function is not True, then the
+          // notification is closed. The notification is also not closed
+          // if an error is thrown.
+          callback: PropTypes.func.isRequired,
+
+          // The label to appear on the button.
+          label: PropTypes.string.isRequired,
+
+          // The accesskey attribute set on the <button> element.
+          accesskey: PropTypes.string,
+        })),
+
+        // A function to call to notify you of interesting things that happen
+        // with the notification box.
+        eventCallback: PropTypes.func,
       })),
 
-      // A function to call to notify you of interesting things that happen
-      // with the notification box.
-      eventCallback: PropTypes.func,
-    })),
+      // Message that should be shown when hovering over the close button
+      closeButtonTooltip: PropTypes.string
+    };
+  }
 
-    // Message that should be shown when hovering over the close button
-    closeButtonTooltip: PropTypes.string
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       closeButtonTooltip: l10n.getStr("notificationBox.closeTooltip")
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       notifications: new Immutable.OrderedMap()
     };
-  },
+
+    this.appendNotification = this.appendNotification.bind(this);
+    this.removeNotification = this.removeNotification.bind(this);
+    this.getNotificationWithValue = this.getNotificationWithValue.bind(this);
+    this.getCurrentNotification = this.getCurrentNotification.bind(this);
+    this.close = this.close.bind(this);
+    this.renderButton = this.renderButton.bind(this);
+    this.renderNotification = this.renderNotification.bind(this);
+  }
 
   /**
    * Create a new notification and display it. If another notification is
    * already present with a higher priority, the new notification will be
    * added behind it. See `propTypes` for arguments description.
    */
-  appendNotification(label, value, image, priority, buttons = [],
-    eventCallback) {
+  appendNotification(label, value, image, priority, buttons = [], eventCallback) {
     // Priority level must be within expected interval
     // (see priority levels at the top of this file).
     if (priority < PriorityLevels.PRIORITY_INFO_LOW ||
       priority > PriorityLevels.PRIORITY_CRITICAL_BLOCK) {
       throw new Error("Invalid notification priority " + priority);
     }
 
     // Custom image URL is not supported yet.
@@ -132,24 +141,24 @@ var NotificationBox = createClass({
     // High priorities must be on top.
     notifications = notifications.sortBy((val, key) => {
       return -val.priority;
     });
 
     this.setState({
       notifications: notifications
     });
-  },
+  }
 
   /**
    * Remove specific notification from the list.
    */
   removeNotification(notification) {
     this.close(this.state.notifications.get(notification.value));
-  },
+  }
 
   /**
    * Returns an object that represents a notification. It can be
    * used to close it.
    */
   getNotificationWithValue(value) {
     let notification = this.state.notifications.get(value);
     if (!notification) {
@@ -158,38 +167,38 @@ var NotificationBox = createClass({
 
     // Return an object that can be used to remove the notification
     // later (using `removeNotification` method) or directly close it.
     return Object.assign({}, notification, {
       close: () => {
         this.close(notification);
       }
     });
-  },
+  }
 
   getCurrentNotification() {
     return this.state.notifications.first();
-  },
+  }
 
   /**
    * Close specified notification.
    */
   close(notification) {
     if (!notification) {
       return;
     }
 
     if (notification.eventCallback) {
       notification.eventCallback("removed");
     }
 
     this.setState({
       notifications: this.state.notifications.remove(notification.value)
     });
-  },
+  }
 
   /**
    * Render a button. A notification can have a set of custom buttons.
    * These are used to execute custom callback.
    */
   renderButton(props, notification) {
     let onClick = event => {
       if (props.callback) {
@@ -205,17 +214,17 @@ var NotificationBox = createClass({
       button({
         key: props.label,
         className: "notification-button",
         accesskey: props.accesskey,
         onClick: onClick},
         props.label
       )
     );
-  },
+  }
 
   /**
    * Render a notification.
    */
   renderNotification(notification) {
     return (
       div({
         key: notification.value,
@@ -236,28 +245,28 @@ var NotificationBox = createClass({
           div({
             className: "messageCloseButton",
             title: this.props.closeButtonTooltip,
             onClick: this.close.bind(this, notification)}
           )
         )
       )
     );
-  },
+  }
 
   /**
    * Render the top (highest priority) notification. Only one
    * notification is rendered at a time.
    */
   render() {
     let notification = this.state.notifications.first();
     let content = notification ?
       this.renderNotification(notification) :
       null;
 
     return div({className: "notificationbox"},
       content
     );
-  },
-});
+  }
+}
 
 module.exports.NotificationBox = NotificationBox;
 module.exports.PriorityLevels = PriorityLevels;
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -1,66 +1,71 @@
 /* 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, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM: dom, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
 
-/**
- * A generic search box component for use across devtools
- */
-module.exports = createClass({
-  displayName: "SearchBox",
+class SearchBox extends Component {
+  static get propTypes() {
+    return {
+      delay: PropTypes.number,
+      keyShortcut: PropTypes.string,
+      onChange: PropTypes.func,
+      placeholder: PropTypes.string,
+      type: PropTypes.string,
+      autocompleteProvider: PropTypes.func,
+    };
+  }
 
-  propTypes: {
-    delay: PropTypes.number,
-    keyShortcut: PropTypes.string,
-    onChange: PropTypes.func,
-    placeholder: PropTypes.string,
-    type: PropTypes.string,
-    autocompleteProvider: PropTypes.func,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState() {
-    return {
+    this.state = {
       value: "",
       focused: false,
     };
-  },
+
+    this.onChange = this.onChange.bind(this);
+    this.onClearButtonClick = this.onClearButtonClick.bind(this);
+    this.onFocus = this.onFocus.bind(this);
+    this.onBlur = this.onBlur.bind(this);
+    this.onKeyDown = this.onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     if (!this.props.keyShortcut) {
       return;
     }
 
     this.shortcuts = new KeyShortcuts({
       window
     });
     this.shortcuts.on(this.props.keyShortcut, (name, event) => {
       event.preventDefault();
       this.refs.input.focus();
     });
-  },
+  }
 
   componentWillUnmount() {
     if (this.shortcuts) {
       this.shortcuts.destroy();
     }
 
     // Clean up an existing timeout.
     if (this.searchTimeout) {
       clearTimeout(this.searchTimeout);
     }
-  },
+  }
 
   onChange() {
     if (this.state.value !== this.refs.input.value) {
       this.setState({
         focused: true,
         value: this.refs.input.value,
       });
     }
@@ -76,30 +81,30 @@ module.exports = createClass({
     }
 
     // Execute the search after a timeout. It makes the UX
     // smoother if the user is typing quickly.
     this.searchTimeout = setTimeout(() => {
       this.searchTimeout = null;
       this.props.onChange(this.state.value);
     }, this.props.delay);
-  },
+  }
 
   onClearButtonClick() {
     this.refs.input.value = "";
     this.onChange();
-  },
+  }
 
   onFocus() {
     this.setState({ focused: true });
-  },
+  }
 
   onBlur() {
     this.setState({ focused: false });
-  },
+  }
 
   onKeyDown(e) {
     let { autocomplete } = this.refs;
     if (!autocomplete || autocomplete.state.list.length <= 0) {
       return;
     }
 
     switch (e.key) {
@@ -126,17 +131,17 @@ module.exports = createClass({
         break;
       case "Home":
         autocomplete.jumpToTop();
         break;
       case "End":
         autocomplete.jumpToBottom();
         break;
     }
-  },
+  }
 
   render() {
     let {
       type = "search",
       placeholder,
       autocompleteProvider,
     } = this.props;
     let { value } = this.state;
@@ -170,9 +175,11 @@ module.exports = createClass({
         ref: "autocomplete",
         onItemSelected: (itemValue) => {
           this.setState({ value: itemValue });
           this.onChange();
         }
       })
     );
   }
-});
+}
+
+module.exports = SearchBox;
--- a/devtools/client/shared/components/SidebarToggle.js
+++ b/devtools/client/shared/components/SidebarToggle.js
@@ -1,54 +1,58 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
+const { DOM, Component, PropTypes } = require("devtools/client/shared/vendor/react");
 
 // Shortcuts
 const { button } = DOM;
 
 /**
  * Sidebar toggle button. This button is used to exapand
  * and collapse Sidebar.
  */
-var SidebarToggle = createClass({
-  displayName: "SidebarToggle",
+class SidebarToggle extends Component {
+  static get propTypes() {
+    return {
+      // Set to true if collapsed.
+      collapsed: PropTypes.bool.isRequired,
+      // Tooltip text used when the button indicates expanded state.
+      collapsePaneTitle: PropTypes.string.isRequired,
+      // Tooltip text used when the button indicates collapsed state.
+      expandPaneTitle: PropTypes.string.isRequired,
+      // Click callback
+      onClick: PropTypes.func.isRequired,
+    };
+  }
 
-  propTypes: {
-    // Set to true if collapsed.
-    collapsed: PropTypes.bool.isRequired,
-    // Tooltip text used when the button indicates expanded state.
-    collapsePaneTitle: PropTypes.string.isRequired,
-    // Tooltip text used when the button indicates collapsed state.
-    expandPaneTitle: PropTypes.string.isRequired,
-    // Click callback
-    onClick: PropTypes.func.isRequired,
-  },
+  constructor(props) {
+    super(props);
 
-  getInitialState: function () {
-    return {
-      collapsed: this.props.collapsed,
+    this.state = {
+      collapsed: props.collapsed,
     };
-  },
+
+    this.onClick = this.onClick.bind(this);
+  }
 
   // Events
 
-  onClick: function (event) {
+  onClick(event) {
     this.props.onClick(event);
-  },
+  }
 
   // Rendering
 
-  render: function () {
+  render() {
     let title = this.state.collapsed ?
       this.props.expandPaneTitle :
       this.props.collapsePaneTitle;
 
     let classNames = ["devtools-button", "sidebar-toggle"];
     if (this.state.collapsed) {
       classNames.push("pane-collapsed");
     }
@@ -56,11 +60,11 @@ var SidebarToggle = createClass({
     return (
       button({
         className: classNames.join(" "),
         title: title,
         onClick: this.onClick
       })
     );
   }
-});
+}
 
 module.exports = SidebarToggle;
--- a/devtools/client/shared/components/StackTrace.js
+++ b/devtools/client/shared/components/StackTrace.js
@@ -1,48 +1,48 @@
 /* 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 React = require("devtools/client/shared/vendor/react");
-const { DOM: dom, createClass, createFactory, PropTypes } = React;
+const { DOM: dom, Component, createFactory, PropTypes } = React;
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const Frame = createFactory(require("./Frame"));
 
 const l10n = new LocalizationHelper("devtools/client/locales/webconsole.properties");
 
-const AsyncFrame = createFactory(createClass({
-  displayName: "AsyncFrame",
-
-  propTypes: {
-    asyncCause: PropTypes.string.isRequired
-  },
+class AsyncFrameClass extends Component {
+  static get propTypes() {
+    return {
+      asyncCause: PropTypes.string.isRequired
+    };
+  }
 
   render() {
     let { asyncCause } = this.props;
 
     return dom.span(
       { className: "frame-link-async-cause" },
       l10n.getFormatStr("stacktrace.asyncStack", asyncCause)
     );
   }
-}));
-
-const StackTrace = createClass({
-  displayName: "StackTrace",
+}
 
-  propTypes: {
-    stacktrace: PropTypes.array.isRequired,
-    onViewSourceInDebugger: PropTypes.func.isRequired,
-    onViewSourceInScratchpad: PropTypes.func,
-    // Service to enable the source map feature.
-    sourceMapService: PropTypes.object,
-  },
+class StackTrace extends Component {
+  static get propTypes() {
+    return {
+      stacktrace: PropTypes.array.isRequired,
+      onViewSourceInDebugger: PropTypes.func.isRequired,
+      onViewSourceInScratchpad: PropTypes.func,
+      // Service to enable the source map feature.
+      sourceMapService: PropTypes.object,
+    };
+  }
 
   render() {
     let {
       stacktrace,
       onViewSourceInDebugger,
       onViewSourceInScratchpad,
       sourceMapService,
     } = this.props;
@@ -72,11 +72,13 @@ const StackTrace = createClass({
           ? onViewSourceInScratchpad
           : onViewSourceInDebugger,
         sourceMapService,
       }), "\n");
     });
 
     return dom.div({ className: "stack-trace" }, frames);
   }
-});
+}
+
+const AsyncFrame = createFactory(AsyncFrameClass);
 
 module.exports = StackTrace;
--- a/devtools/client/shared/components/Tree.js
+++ b/devtools/client/shared/components/Tree.js
@@ -1,16 +1,16 @@
 /* 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 React = require("devtools/client/shared/vendor/react");
-const { DOM: dom, createClass, createFactory, PropTypes } = React;
+const { DOM: dom, Component, createFactory, PropTypes } = React;
 
 const AUTO_EXPAND_DEPTH = 0;
 const NUMBER_OF_OFFSCREEN_ITEMS = 1;
 
 /**
  * A fast, generic, expandable and collapsible tree component.
  *
  * This tree component is fast: it can handle trees with *many* items. It only
@@ -92,168 +92,186 @@ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
  *           },
  *
  *           onExpand: item => dispatchExpandActionToRedux(item),
  *           onCollapse: item => dispatchCollapseActionToRedux(item),
  *         });
  *       }
  *     });
  */
-module.exports = createClass({
-  displayName: "Tree",
-
-  propTypes: {
-    // Required props
+class Tree extends Component {
+  static get propTypes() {
+    return {
+      // Required props
 
-    // A function to get an item's parent, or null if it is a root.
-    //
-    // Type: getParent(item: Item) -> Maybe<Item>
-    //
-    // Example:
-    //
-    //     // The parent of this item is stored in its `parent` property.
-    //     getParent: item => item.parent
-    getParent: PropTypes.func.isRequired,
+      // A function to get an item's parent, or null if it is a root.
+      //
+      // Type: getParent(item: Item) -> Maybe<Item>
+      //
+      // Example:
+      //
+      //     // The parent of this item is stored in its `parent` property.
+      //     getParent: item => item.parent
+      getParent: PropTypes.func.isRequired,
 
-    // A function to get an item's children.
-    //
-    // Type: getChildren(item: Item) -> [Item]
-    //
-    // Example:
-    //
-    //     // This item's children are stored in its `children` property.
-    //     getChildren: item => item.children
-    getChildren: PropTypes.func.isRequired,
+      // A function to get an item's children.
+      //
+      // Type: getChildren(item: Item) -> [Item]
+      //
+      // Example:
+      //
+      //     // This item's children are stored in its `children` property.
+      //     getChildren: item => item.children
+      getChildren: PropTypes.func.isRequired,
 
-    // A function which takes an item and ArrowExpander component instance and
-    // returns a component, or text, or anything else that React considers
-    // renderable.
-    //
-    // Type: renderItem(item: Item,
-    //                  depth: Number,
-    //                  isFocused: Boolean,
-    //                  arrow: ReactComponent,
-    //                  isExpanded: Boolean) -> ReactRenderable
-    //
-    // Example:
-    //
-    //     renderItem: (item, depth, isFocused, arrow, isExpanded) => {
-    //       let className = "my-tree-item";
-    //       if (isFocused) {
-    //         className += " focused";
-    //       }
-    //       return dom.div(
-    //         {
-    //           className,
-    //           style: { marginLeft: depth * 10 + "px" }
-    //         },
-    //         arrow,
-    //         dom.span({ className: "my-tree-item-label" }, item.label)
-    //       );
-    //     },
-    renderItem: PropTypes.func.isRequired,
+      // A function which takes an item and ArrowExpander component instance and
+      // returns a component, or text, or anything else that React considers
+      // renderable.
+      //
+      // Type: renderItem(item: Item,
+      //                  depth: Number,
+      //                  isFocused: Boolean,
+      //                  arrow: ReactComponent,
+      //                  isExpanded: Boolean) -> ReactRenderable
+      //
+      // Example:
+      //
+      //     renderItem: (item, depth, isFocused, arrow, isExpanded) => {
+      //       let className = "my-tree-item";
+      //       if (isFocused) {
+      //         className += " focused";
+      //       }
+      //       return dom.div(
+      //         {
+      //           className,
+      //           style: { marginLeft: depth * 10 + "px" }
+      //         },
+      //         arrow,
+      //         dom.span({ className: "my-tree-item-label" }, item.label)
+      //       );
+      //     },
+      renderItem: PropTypes.func.isRequired,
 
-    // A function which returns the roots of the tree (forest).
-    //
-    // Type: getRoots() -> [Item]
-    //
-    // Example:
-    //
-    //     // In this case, we only have one top level, root item. You could
-    //     // return multiple items if you have many top level items in your
-    //     // tree.
-    //     getRoots: () => [this.props.rootOfMyTree]
-    getRoots: PropTypes.func.isRequired,
+      // A function which returns the roots of the tree (forest).
+      //
+      // Type: getRoots() -> [Item]
+      //
+      // Example:
+      //
+      //     // In this case, we only have one top level, root item. You could
+      //     // return multiple items if you have many top level items in your
+      //     // tree.
+      //     getRoots: () => [this.props.rootOfMyTree]
+      getRoots: PropTypes.func.isRequired,
 
-    // A function to get a unique key for the given item. This helps speed up
-    // React's rendering a *TON*.
-    //
-    // Type: getKey(item: Item) -> String
-    //
-    // Example:
-    //
-    //     getKey: item => `my-tree-item-${item.uniqueId}`
-    getKey: PropTypes.func.isRequired,
+      // A function to get a unique key for the given item. This helps speed up
+      // React's rendering a *TON*.
+      //
+      // Type: getKey(item: Item) -> String
+      //
+      // Example:
+      //
+      //     getKey: item => `my-tree-item-${item.uniqueId}`
+      getKey: PropTypes.func.isRequired,
 
-    // A function to get whether an item is expanded or not. If an item is not
-    // expanded, then it must be collapsed.
-    //
-    // Type: isExpanded(item: Item) -> Boolean
-    //
-    // Example:
-    //
-    //     isExpanded: item => item.expanded,
-    isExpanded: PropTypes.func.isRequired,
+      // A function to get whether an item is expanded or not. If an item is not
+      // expanded, then it must be collapsed.
+      //
+      // Type: isExpanded(item: Item) -> Boolean
+      //
+      // Example:
+      //
+      //     isExpanded: item => item.expanded,
+      isExpanded: PropTypes.func.isRequired,
 
-    // The height of an item in the tree including margin and padding, in
-    // pixels.
-    itemHeight: PropTypes.number.isRequired,
+      // The height of an item in the tree including margin and padding, in
+      // pixels.
+      itemHeight: PropTypes.number.isRequired,
 
-    // Optional props
+      // Optional props
 
-    // The currently focused item, if any such item exists.
-    focused: PropTypes.any,
+      // The currently focused item, if any such item exists.
+      focused: PropTypes.any,
 
-    // Handle when a new item is focused.
-    onFocus: PropTypes.func,
+      // Handle when a new item is focused.
+      onFocus: PropTypes.func,
 
-    // The depth to which we should automatically expand new items.
-    autoExpandDepth: PropTypes.number,
+      // The depth to which we should automatically expand new items.
+      autoExpandDepth: PropTypes.number,
 
-    // Note: the two properties below are mutually exclusive. Only one of the
-    // label properties is necessary.
-    // ID of an element whose textual content serves as an accessible label for
-    // a tree.
-    labelledby: PropTypes.string,
-    // Accessibility label for a tree widget.
-    label: PropTypes.string,
+      // Note: the two properties below are mutually exclusive. Only one of the
+      // label properties is necessary.
+      // ID of an element whose textual content serves as an accessible label for
+      // a tree.
+      labelledby: PropTypes.string,
+      // Accessibility label for a tree widget.
+      label: PropTypes.string,
 
-    // Optional event handlers for when items are expanded or collapsed. Useful
-    // for dispatching redux events and updating application state, maybe lazily
-    // loading subtrees from a worker, etc.
-    //
-    // Type:
-    //     onExpand(item: Item)
-    //     onCollapse(item: Item)
-    //
-    // Example:
-    //
-    //     onExpand: item => dispatchExpandActionToRedux(item)
-    onExpand: PropTypes.func,
-    onCollapse: PropTypes.func,
-  },
+      // Optional event handlers for when items are expanded or collapsed. Useful
+      // for dispatching redux events and updating application state, maybe lazily
+      // loading subtrees from a worker, etc.
+      //
+      // Type:
+      //     onExpand(item: Item)
+      //     onCollapse(item: Item)
+      //
+      // Example:
+      //
+      //     onExpand: item => dispatchExpandActionToRedux(item)
+      onExpand: PropTypes.func,
+      onCollapse: PropTypes.func,
+    };
+  }
 
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       autoExpandDepth: AUTO_EXPAND_DEPTH,
     };
-  },
+  }
 
-  getInitialState() {
-    return {
+  constructor(props) {
+    super(props);
+
+    this.state = {
       scroll: 0,
       height: window.innerHeight,
       seen: new Set(),
     };
-  },
+
+    this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
+    this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
+    this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
+    this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
+    this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
+    this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
+
+    this._autoExpand = this._autoExpand.bind(this);
+    this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
+    this._updateHeight = this._updateHeight.bind(this);
+    this._dfs = this._dfs.bind(this);
+    this._dfsFromRoots = this._dfsFromRoots.bind(this);
+    this._focus = this._focus.bind(this);
+    this._onBlur = this._onBlur.bind(this);
+    this._onKeyDown = this._onKeyDown.bind(this);
+  }
 
   componentDidMount() {
     window.addEventListener("resize", this._updateHeight);
     this._autoExpand();
     this._updateHeight();
-  },
+  }
 
   componentWillReceiveProps(nextProps) {
     this._autoExpand();
     this._updateHeight();
-  },
+  }
 
   componentWillUnmount() {
     window.removeEventListener("resize", this._updateHeight);
-  },
+  }
 
   _autoExpand() {
     if (!this.props.autoExpandDepth) {
       return;
     }
 
     // Automatically expand the first autoExpandDepth levels for new items. Do
     // not use the usual DFS infrastructure because we don't want to ignore
@@ -274,17 +292,17 @@ module.exports = createClass({
       }
     };
 
     const roots = this.props.getRoots();
     const length = roots.length;
     for (let i = 0; i < length; i++) {
       autoExpand(roots[i], 0);
     }
-  },
+  }
 
   _preventArrowKeyScrolling(e) {
     switch (e.key) {
       case "ArrowUp":
       case "ArrowDown":
       case "ArrowLeft":
       case "ArrowRight":
         e.preventDefault();
@@ -293,26 +311,26 @@ module.exports = createClass({
           if (e.nativeEvent.preventDefault) {
             e.nativeEvent.preventDefault();
           }
           if (e.nativeEvent.stopPropagation) {
             e.nativeEvent.stopPropagation();
           }
         }
     }
-  },
+  }
 
   /**
    * Updates the state's height based on clientHeight.
    */
   _updateHeight() {
     this.setState({
       height: this.refs.tree.clientHeight
     });
-  },
+  }
 
   /**
    * Perform a pre-order depth-first search from item.
    */
   _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
     traversal.push({ item, depth: _depth });
 
     if (!this.props.isExpanded(item)) {
@@ -327,63 +345,63 @@ module.exports = createClass({
 
     const children = this.props.getChildren(item);
     const length = children.length;
     for (let i = 0; i < length; i++) {
       this._dfs(children[i], maxDepth, traversal, nextDepth);
     }
 
     return traversal;
-  },
+  }
 
   /**
    * Perform a pre-order depth-first search over the whole forest.
    */
   _dfsFromRoots(maxDepth = Infinity) {
     const traversal = [];
 
     const roots = this.props.getRoots();
     const length = roots.length;
     for (let i = 0; i < length; i++) {
       this._dfs(roots[i], maxDepth, traversal);
     }
 
     return traversal;
-  },
+  }
 
   /**
    * Expands current row.
    *
    * @param {Object} item
    * @param {Boolean} expandAllChildren
    */
-  _onExpand: oncePerAnimationFrame(function (item, expandAllChildren) {
+  _onExpand(item, expandAllChildren) {
     if (this.props.onExpand) {
       this.props.onExpand(item);
 
       if (expandAllChildren) {
         const children = this._dfs(item);
         const length = children.length;
         for (let i = 0; i < length; i++) {
           this.props.onExpand(children[i].item);
         }
       }
     }
-  }),
+  }
 
   /**
    * Collapses current row.
    *
    * @param {Object} item
    */
-  _onCollapse: oncePerAnimationFrame(function (item) {
+  _onCollapse(item) {
     if (this.props.onCollapse) {
       this.props.onCollapse(item);
     }
-  }),
+  }
 
   /**
    * Sets the passed in item to be the focused item.
    *
    * @param {Number} index
    *        The index of the item in a full DFS traversal (ignoring collapsed
    *        nodes). Ignored if `item` is undefined.
    *
@@ -406,37 +424,37 @@ module.exports = createClass({
       } else if ((this.state.scroll + this.state.height) < itemEndPosition) {
         this.refs.tree.scrollTo(0, itemEndPosition - this.state.height);
       }
     }
 
     if (this.props.onFocus) {
       this.props.onFocus(item);
     }
-  },
+  }
 
   /**
    * Sets the state to have no focused item.
    */
   _onBlur() {
     this._focus(0, undefined);
-  },
+  }
 
   /**
    * Fired on a scroll within the tree's container, updates
    * the stored position of the view port to handle virtual view rendering.
    *
    * @param {Event} e
    */
-  _onScroll: oncePerAnimationFrame(function (e) {
+  _onScroll(e) {
     this.setState({
       scroll: Math.max(this.refs.tree.scrollTop, 0),
       height: this.refs.tree.clientHeight
     });
-  }),
+  }
 
   /**
    * Handles key down events in the tree's container.
    *
    * @param {Event} e
    */
   _onKeyDown(e) {
     if (this.props.focused == null) {
@@ -471,22 +489,22 @@ module.exports = createClass({
       case "ArrowRight":
         if (!this.props.isExpanded(this.props.focused)) {
           this._onExpand(this.props.focused);
         } else {
           this._focusNextNode();
         }
         break;
     }
-  },
+  }
 
   /**
    * Sets the previous node relative to the currently focused item, to focused.
    */
-  _focusPrevNode: oncePerAnimationFrame(function () {
+  _focusPrevNode() {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the previous node in the DFS, if it exists. If it
     // doesn't exist, we're at the first node already.
 
     let prev;
     let prevIndex;
 
     const traversal = this._dfsFromRoots();
@@ -500,23 +518,23 @@ module.exports = createClass({
       prevIndex = i;
     }
 
     if (prev === undefined) {
       return;
     }
 
     this._focus(prevIndex, prev);
-  }),
+  }
 
   /**
    * Handles the down arrow key which will focus either the next child
    * or sibling row.
    */
-  _focusNextNode: oncePerAnimationFrame(function () {
+  _focusNextNode() {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the next node in the DFS, if it exists. If it
     // doesn't exist, we're at the last node already.
 
     const traversal = this._dfsFromRoots();
     const length = traversal.length;
     let i = 0;
 
@@ -525,39 +543,39 @@ module.exports = createClass({
         break;
       }
       i++;
     }
 
     if (i + 1 < traversal.length) {
       this._focus(i + 1, traversal[i + 1].item);
     }
-  }),
+  }
 
   /**
    * Handles the left arrow key, going back up to the current rows'
    * parent row.
    */
-  _focusParentNode: oncePerAnimationFrame(function () {
+  _focusParentNode() {
     const parent = this.props.getParent(this.props.focused);
     if (!parent) {
       return;
     }
 
     const traversal = this._dfsFromRoots();
     const length = traversal.length;
     let parentIndex = 0;
     for (; parentIndex < length; parentIndex++) {
       if (traversal[parentIndex].item === parent) {
         break;
       }
     }
 
     this._focus(parentIndex, parent);
-  }),
+  }
 
   render() {
     const traversal = this._dfsFromRoots();
 
     // 'begin' and 'end' are the index of the first (at least partially) visible item
     // and the index after the last (at least partially) visible item, respectively.
     // `NUMBER_OF_OFFSCREEN_ITEMS` is removed from `begin` and added to `end` so that
     // the top and bottom of the page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS`
@@ -651,38 +669,38 @@ module.exports = createClass({
         style: {
           padding: 0,
           margin: 0
         }
       },
       nodes
     );
   }
-});
+}
 
 /**
  * An arrow that displays whether its node is expanded (▼) or collapsed
  * (▶). When its node has no children, it is hidden.
  */
-const ArrowExpander = createFactory(createClass({
-  displayName: "ArrowExpander",
-
-  propTypes: {
-    item: PropTypes.any.isRequired,
-    visible: PropTypes.bool.isRequired,
-    expanded: PropTypes.bool.isRequired,
-    onCollapse: PropTypes.func.isRequired,
-    onExpand: PropTypes.func.isRequired,
-  },
+class ArrowExpanderClass extends Component {
+  static get propTypes() {
+    return {
+      item: PropTypes.any.isRequired,
+      visible: PropTypes.bool.isRequired,
+      expanded: PropTypes.bool.isRequired,
+      onCollapse: PropTypes.func.isRequired,
+      onExpand: PropTypes.func.isRequired,
+    };
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.props.item !== nextProps.item
       || this.props.visible !== nextProps.visible
       || this.props.expanded !== nextProps.expanded;
-  },
+  }
 
   render() {
     const attrs = {
       className: "arrow theme-twisty",
       onClick: this.props.expanded
         ? () => this.props.onCollapse(this.props.item)
         : e => this.props.onExpand(this.props.item, e.altKey)
     };
@@ -694,34 +712,36 @@ const ArrowExpander = createFactory(crea
     if (!this.props.visible) {
       attrs.style = {
         visibility: "hidden"
       };
     }
 
     return dom.div(attrs);
   }
-}));
+}
 
-const TreeNode = createFactory(createClass({
-  propTypes: {
-    id: PropTypes.any.isRequired,
-    focused: PropTypes.bool.isRequired,
-    item: PropTypes.any.isRequired,
-    expanded: PropTypes.bool.isRequired,
-    hasChildren: PropTypes.bool.isRequired,
-    onExpand: PropTypes.func.isRequired,
-    index: PropTypes.number.isRequired,
-    first: PropTypes.bool,
-    last: PropTypes.bool,
-    onClick: PropTypes.func,
-    onCollapse: PropTypes.func.isRequired,
-    depth: PropTypes.number.isRequired,
-    renderItem: PropTypes.func.isRequired,
-  },
+class TreeNodeClass extends Component {
+  static get propTypes() {
+    return {
+      id: PropTypes.any.isRequired,
+      focused: PropTypes.bool.isRequired,
+      item: PropTypes.any.isRequired,
+      expanded: PropTypes.bool.isRequired,
+      hasChildren: PropTypes.bool.isRequired,
+      onExpand: PropTypes.func.isRequired,
+      index: PropTypes.number.isRequired,
+      first: PropTypes.bool,
+      last: PropTypes.bool,
+      onClick: PropTypes.func,
+      onCollapse: PropTypes.func.isRequired,
+      depth: PropTypes.number.isRequired,
+      renderItem: PropTypes.func.isRequired,
+    };
+  }
 
   render() {
     const arrow = ArrowExpander({
       item: this.props.item,
       expanded: this.props.expanded,
       visible: this.props.hasChildren,
       onExpand: this.props.onExpand,
       onCollapse: this.props.onCollapse,
@@ -764,17 +784,20 @@ const TreeNode = createFactory(createCla
 
       this.props.renderItem(this.props.item,
                             this.props.depth,
                             this.props.focused,
                             arrow,
                             this.props.expanded),
     );
   }
-}));
+}
+
+const ArrowExpander = createFactory(ArrowExpanderClass);
+const TreeNode = createFactory(TreeNodeClass);
 
 /**
  * Create a function that calls the given function `fn` only once per animation
  * frame.
  *
  * @param {Function} fn
  * @returns {Function}
  */
@@ -789,8 +812,10 @@ function oncePerAnimationFrame(fn) {
 
     animationId = requestAnimationFrame(() => {
       fn.call(this, ...argsToPass);
       animationId = null;
       argsToPass = null;
     });
   };
 }
+
+module.exports = Tree;
--- a/devtools/client/shared/components/splitter/Draggable.js
+++ b/devtools/client/shared/components/splitter/Draggable.js
@@ -1,54 +1,61 @@
 /* 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 React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
-const { DOM: dom, PropTypes } = React;
-
-const Draggable = React.createClass({
-  displayName: "Draggable",
+const { Component, DOM: dom, PropTypes } = React;
 
-  propTypes: {
-    onMove: PropTypes.func.isRequired,
-    onStart: PropTypes.func,
-    onStop: PropTypes.func,
-    style: PropTypes.object,
-    className: PropTypes.string
-  },
+class Draggable extends Component {
+  static get propTypes() {
+    return {
+      onMove: PropTypes.func.isRequired,
+      onStart: PropTypes.func,
+      onStop: PropTypes.func,
+      style: PropTypes.object,
+      className: PropTypes.string
+    };
+  }
+
+  constructor(props) {
+    super(props);
+    this.startDragging = this.startDragging.bind(this);
+    this.onMove = this.onMove.bind(this);
+    this.onUp = this.onUp.bind(this);
+  }
 
   startDragging(ev) {
     ev.preventDefault();
     const doc = ReactDOM.findDOMNode(this).ownerDocument;
     doc.addEventListener("mousemove", this.onMove);
     doc.addEventListener("mouseup", this.onUp);
     this.props.onStart && this.props.onStart();
-  },
+  }
 
   onMove(ev) {
     ev.preventDefault();
     // Use viewport coordinates so, moving mouse over iframes
     // doesn't mangle (relative) coordinates.
     this.props.onMove(ev.clientX, ev.clientY);
-  },
+  }
 
   onUp(ev) {
     ev.preventDefault();
     const doc = ReactDOM.findDOMNode(this).ownerDocument;
     doc.removeEventListener("mousemove", this.onMove);
     doc.removeEventListener("mouseup", this.onUp);
     this.props.onStop && this.props.onStop();
-  },
+  }
 
   render() {
     return dom.div({
       style: this.props.style,
       className: this.props.className,
       onMouseDown: this.startDragging
     });
   }
-});
+}
 
 module.exports = Draggable;
--- a/devtools/client/shared/components/splitter/SplitBox.js
+++ b/devtools/client/shared/components/splitter/SplitBox.js
@@ -2,92 +2,98 @@
  * 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 React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Draggable = React.createFactory(require("devtools/client/shared/components/splitter/Draggable"));
-const { DOM: dom, PropTypes } = React;
+const { Component, DOM: dom, PropTypes } = React;
 
 /**
  * This component represents a Splitter. The splitter supports vertical
  * as well as horizontal mode.
  */
-const SplitBox = React.createClass({
-  displayName: "SplitBox",
+class SplitBox extends Component {
+  static get propTypes() {
+    return {
+      // Custom class name. You can use more names separated by a space.
+      className: PropTypes.string,
+      // Initial size of controlled panel.
+      initialSize: PropTypes.string,
+      // Initial width of controlled panel.
+      initialWidth: PropTypes.string,
+      // Initial height of controlled panel.
+      initialHeight: PropTypes.string,
+      // Left/top panel
+      startPanel: PropTypes.any,
+      // Min panel size.
+      minSize: PropTypes.string,
+      // Max panel size.
+      maxSize: PropTypes.string,
+      // Right/bottom panel
+      endPanel: PropTypes.any,
+      // True if the right/bottom panel should be controlled.
+      endPanelControl: PropTypes.bool,
+      // Size of the splitter handle bar.
+      splitterSize: PropTypes.string,
+      // True if the splitter bar is vertical (default is vertical).
+      vert: PropTypes.bool,
+      // Style object.
+      style: PropTypes.object,
+    };
+  }
 
-  propTypes: {
-    // Custom class name. You can use more names separated by a space.
-    className: PropTypes.string,
-    // Initial size of controlled panel.
-    initialSize: PropTypes.string,
-    // Initial width of controlled panel.
-    initialWidth: PropTypes.string,
-    // Initial height of controlled panel.
-    initialHeight: PropTypes.string,
-    // Left/top panel
-    startPanel: PropTypes.any,
-    // Min panel size.
-    minSize: PropTypes.string,
-    // Max panel size.
-    maxSize: PropTypes.string,
-    // Right/bottom panel
-    endPanel: PropTypes.any,
-    // True if the right/bottom panel should be controlled.
-    endPanelControl: PropTypes.bool,
-    // Size of the splitter handle bar.
-    splitterSize: PropTypes.string,
-    // True if the splitter bar is vertical (default is vertical).
-    vert: PropTypes.bool,
-    // Style object.
-    style: PropTypes.object,
-  },
-
-  getDefaultProps() {
+  static get defaultProps() {
     return {
       splitterSize: 5,
       vert: true,
       endPanelControl: false
     };
-  },
+  }
+
+  constructor(props) {
+    super(props);
 
-  /**
-   * The state stores the current orientation (vertical or horizontal)
-   * and the current size (width/height). All these values can change
-   * during the component's life time.
-   */
-  getInitialState() {
-    return {
-      vert: this.props.vert,
-      width: this.props.initialWidth || this.props.initialSize,
-      height: this.props.initialHeight || this.props.initialSize
+    /**
+     * The state stores the current orientation (vertical or horizontal)
+     * and the current size (width/height). All these values can change
+     * during the component's life time.
+     */
+    this.state = {
+      vert: props.vert,
+      width: props.initialWidth || props.initialSize,
+      height: props.initialHeight || props.initialSize
     };
-  },
+
+    this.onStartMove = this.onStartMove.bind(this);
+    this.onStopMove = this.onStopMove.bind(this);
+    this.onMove = this.onMove.bind(this);
+  }
 
   componentWillReceiveProps(nextProps) {
     let { vert } = nextProps;
 
     if (vert !== this.props.vert) {
       this.setState({ vert });
     }
-  },
+  }
 
   shouldComponentUpdate(nextProps, nextState) {
     return nextState.width != this.state.width ||
       nextState.height != this.state.height ||
       nextState.vert != this.state.vert ||
       nextProps.startPanel != this.props.startPanel ||
       nextProps.endPanel != this.props.endPanel ||
       nextProps.endPanelControl != this.props.endPanelControl ||
       nextProps.minSize != this.props.minSize ||
       nextProps.maxSize != this.props.maxSize ||
       nextProps.splitterSize != this.props.splitterSize;
-  },
+  }
 
   // Dragging Events
 
   /**
    * Set 'resizing' cursor on entire document during splitter dragging.
    * This avoids cursor-flickering that happens when the mouse leaves
    * the splitter bar area (happens frequently).
    */
@@ -97,25 +103,25 @@ const SplitBox = React.createClass({
     let defaultCursor = doc.documentElement.style.cursor;
     doc.documentElement.style.cursor = (this.state.vert ? "ew-resize" : "ns-resize");
 
     splitBox.classList.add("dragging");
 
     this.setState({
       defaultCursor: defaultCursor
     });
-  },
+  }
 
   onStopMove() {
     const splitBox = ReactDOM.findDOMNode(this);
     const doc = splitBox.ownerDocument;
     doc.documentElement.style.cursor = this.state.defaultCursor;
 
     splitBox.classList.remove("dragging");
-  },
+  }
 
   /**
    * Adjust size of the controlled panel. Depending on the current
    * orientation we either remember the width or height of
    * the splitter box.
    */
   onMove(x, y) {
     const node = ReactDOM.findDOMNode(this);
@@ -144,17 +150,17 @@ const SplitBox = React.createClass({
       size = endPanelControl ?
         (node.offsetTop + node.offsetHeight) - y :
         y - node.offsetTop;
 
       this.setState({
         height: size
       });
     }
-  },
+  }
 
   // Rendering
 
   render() {
     const vert = this.state.vert;
     const { startPanel, endPanel, endPanelControl, minSize,
       maxSize, splitterSize } = this.props;
 
@@ -221,11 +227,11 @@ const SplitBox = React.createClass({
           dom.div({
             className: endPanelControl ? "controlled" : "uncontrolled",
             style: rightPanelStyle},
             endPanel
           ) : null
       )
     );
   }
-});
+}
 
 module.exports = SplitBox;
--- a/devtools/client/shared/components/tabs/TabBar.js
+++ b/devtools/client/shared/components/tabs/TabBar.js
@@ -3,87 +3,100 @@
 /* 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, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { DOM, Component, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const Tabs = createFactory(require("devtools/client/shared/components/tabs/Tabs").Tabs);
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
 // Shortcuts
 const { div } = DOM;
 
 /**
  * Renders Tabbar component.
  */
-let Tabbar = createClass({
-  displayName: "Tabbar",
+class Tabbar extends Component {
+  static get propTypes() {
+    return {
+      children: PropTypes.array,
+      menuDocument: PropTypes.object,
+      onSelect: PropTypes.func,
+      showAllTabsMenu: PropTypes.bool,
+      activeTabId: PropTypes.string,
+      renderOnlySelected: PropTypes.bool,
+    };
+  }
 
-  propTypes: {
-    children: PropTypes.array,
-    menuDocument: PropTypes.object,
-    onSelect: PropTypes.func,
-    showAllTabsMenu: PropTypes.bool,
-    activeTabId: PropTypes.string,
-    renderOnlySelected: PropTypes.bool,
-  },
-
-  getDefaultProps: function () {
+  static get defaultProps() {
     return {
       menuDocument: window.parent.document,
       showAllTabsMenu: false,
     };
-  },
+  }
 
-  getInitialState: function () {
-    let { activeTabId, children = [] } = this.props;
+  constructor(props, context) {
+    super(props, context);
+    let { activeTabId, children = [] } = props;
     let tabs = this.createTabs(children);
     let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
-    return {
+    this.state = {
       activeTab: activeTab === -1 ? 0 : activeTab,
       tabs,
     };
-  },
 
-  componentWillReceiveProps: function (nextProps) {
+    this.createTabs = this.createTabs.bind(this);
+    this.addTab = this.addTab.bind(this);
+    this.toggleTab = this.toggleTab.bind(this);
+    this.removeTab = this.removeTab.bind(this);
+    this.select = this.select.bind(this);
+    this.getTabIndex = this.getTabIndex.bind(this);
+    this.getTabId = this.getTabId.bind(this);
+    this.getCurrentTabId = this.getCurrentTabId.bind(this);
+    this.onTabChanged = this.onTabChanged.bind(this);
+    this.onAllTabsMenuClick = this.onAllTabsMenuClick.bind(this);
+    this.renderTab = this.renderTab.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
     let { activeTabId, children = [] } = nextProps;
     let tabs = this.createTabs(children);
     let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
     if (activeTab !== this.state.activeTab ||
         (children !== this.props.children)) {
       this.setState({
         activeTab: activeTab === -1 ? 0 : activeTab,
         tabs,
       });
     }
-  },
+  }
 
-  createTabs: function (children) {
+  createTabs(children) {
     return children
       .filter((panel) => panel)
       .map((panel, index) =>
         Object.assign({}, children[index], {
           id: panel.props.id || index,
           panel,
           title: panel.props.title,
         })
       );
-  },
+  }
 
   // Public API
 
-  addTab: function (id, title, selected = false, panel, url, index = -1) {
+  addTab(id, title, selected = false, panel, url, index = -1) {
     let tabs = this.state.tabs.slice();
 
     if (index >= 0) {
       tabs.splice(index, 0, {id, title, panel, url});
     } else {
       tabs.push({id, title, panel, url});
     }
 
@@ -95,35 +108,35 @@ let Tabbar = createClass({
       newState.activeTab = index >= 0 ? index : tabs.length - 1;
     }
 
     this.setState(newState, () => {
       if (this.props.onSelect && selected) {
         this.props.onSelect(id);
       }
     });
-  },
+  }
 
-  toggleTab: function (tabId, isVisible) {
+  toggleTab(tabId, isVisible) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let tabs = this.state.tabs.slice();
     tabs[index] = Object.assign({}, tabs[index], {
       isVisible: isVisible
     });
 
     this.setState(Object.assign({}, this.state, {
       tabs: tabs,
     }));
-  },
+  }
 
-  removeTab: function (tabId) {
+  removeTab(tabId) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let tabs = this.state.tabs.slice();
     tabs.splice(index, 1);
 
@@ -132,68 +145,68 @@ let Tabbar = createClass({
     if (activeTab >= tabs.length) {
       activeTab = tabs.length - 1;
     }
 
     this.setState(Object.assign({}, this.state, {
       tabs,
       activeTab,
     }));
-  },
+  }
 
-  select: function (tabId) {
+  select(tabId) {
     let index = this.getTabIndex(tabId);
     if (index < 0) {
       return;
     }
 
     let newState = Object.assign({}, this.state, {
       activeTab: index,
     });
 
     this.setState(newState, () => {
       if (this.props.onSelect) {
         this.props.onSelect(tabId);
       }
     });
-  },
+  }
 
   // Helpers
 
-  getTabIndex: function (tabId) {
+  getTabIndex(tabId) {
     let tabIndex = -1;
     this.state.tabs.forEach((tab, index) => {
       if (tab.id === tabId) {
         tabIndex = index;
       }
     });
     return tabIndex;
-  },
+  }
 
-  getTabId: function (index) {
+  getTabId(index) {
     return this.state.tabs[index].id;
-  },
+  }
 
-  getCurrentTabId: function () {
+  getCurrentTabId() {
     return this.state.tabs[this.state.activeTab].id;
-  },
+  }
 
   // Event Handlers
 
-  onTabChanged: function (index) {
+  onTabChanged(index) {
     this.setState({
       activeTab: index
     });
 
     if (this.props.onSelect) {
       this.props.onSelect(this.state.tabs[index].id);
     }
-  },
+  }
 
-  onAllTabsMenuClick: function (event) {
+  onAllTabsMenuClick(event) {
     let menu = new Menu();
     let target = event.target;
 
     // Generate list of menu items from the list of tabs.
     this.state.tabs.forEach((tab) => {
       menu.append(new MenuItem({
         label: tab.title,
         type: "checkbox",
@@ -209,45 +222,45 @@ let Tabbar = createClass({
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
     let rect = target.getBoundingClientRect();
     let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
     let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
     menu.popup(rect.left + screenX, rect.bottom + screenY,
       { doc: this.props.menuDocument });
 
     return menu;
-  },
+  }
 
   // Rendering
 
-  renderTab: function (tab) {
+  renderTab(tab) {
     if (typeof tab.panel === "function") {
       return tab.panel({
         key: tab.id,
         title: tab.title,
         id: tab.id,
         url: tab.url,
       });
     }
 
     return tab.panel;
-  },
+  }
 
-  render: function () {
+  render() {
     let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
 
     return (
       div({className: "devtools-sidebar-tabs"},
         Tabs({
           onAllTabsMenuClick: this.onAllTabsMenuClick,
           renderOnlySelected: this.props.renderOnlySelected,
           showAllTabsMenu: this.props.showAllTabsMenu,
           tabActive: this.state.activeTab,
           onAfterChange: this.onTabChanged,
         },
           tabs
         )
       )
     );
-  },
-});
+  }
+}
 
 module.exports = Tabbar;
--- a/devtools/client/shared/components/tabs/Tabs.js
+++ b/devtools/client/shared/components/tabs/Tabs.js
@@ -3,17 +3,17 @@
 /* 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";
 
 define(function (require, exports, module) {
   const React = require("devtools/client/shared/vendor/react");
-  const { DOM } = React;
+  const { Component, DOM } = React;
   const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 
   /**
    * Renders simple 'tab' widget.
    *
    * Based on ReactSimpleTabs component
    * https://github.com/pedronauck/react-simpletabs
    *
@@ -26,89 +26,99 @@ define(function (require, exports, modul
    *      <li class='tabs-menu-item'>Tab #2</li>
    *    </ul>
    *  </nav>
    *  <div class='panels'>
    *    The content of active panel here
    *  </div>
    * <div>
    */
-  let Tabs = React.createClass({
-    displayName: "Tabs",
+  class Tabs extends Component {
+    static get propTypes() {
+      return {
+        className: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.string,
+          React.PropTypes.object
+        ]),
+        tabActive: React.PropTypes.number,
+        onMount: React.PropTypes.func,
+        onBeforeChange: React.PropTypes.func,
+        onAfterChange: React.PropTypes.func,
+        children: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.element
+        ]).isRequired,
+        showAllTabsMenu: React.PropTypes.bool,
+        onAllTabsMenuClick: React.PropTypes.func,
 
-    propTypes: {
-      className: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.string,
-        React.PropTypes.object
-      ]),
-      tabActive: React.PropTypes.number,
-      onMount: React.PropTypes.func,
-      onBeforeChange: React.PropTypes.func,
-      onAfterChange: React.PropTypes.func,
-      children: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.element
-      ]).isRequired,
-      showAllTabsMenu: React.PropTypes.bool,
-      onAllTabsMenuClick: React.PropTypes.func,
+        // Set true will only render selected panel on DOM. It's complete
+        // opposite of the created array, and it's useful if panels content
+        // is unpredictable and update frequently.
+        renderOnlySelected: React.PropTypes.bool,
+      };
+    }
 
-      // Set true will only render selected panel on DOM. It's complete
-      // opposite of the created array, and it's useful if panels content
-      // is unpredictable and update frequently.
-      renderOnlySelected: React.PropTypes.bool,
-    },
-
-    getDefaultProps: function () {
+    static get defaultProps() {
       return {
         tabActive: 0,
         showAllTabsMenu: false,
         renderOnlySelected: false,
       };
-    },
+    }
 
-    getInitialState: function () {
-      return {
-        tabActive: this.props.tabActive,
+    constructor(props) {
+      super(props);
+
+      this.state = {
+        tabActive: props.tabActive,
 
         // This array is used to store an information whether a tab
         // at specific index has already been created (e.g. selected
         // at least once).
         // If yes, it's rendered even if not currently selected.
         // This is because in some cases we don't want to re-create
         // tab content when it's being unselected/selected.
         // E.g. in case of an iframe being used as a tab-content
         // we want the iframe to stay in the DOM.
         created: [],
 
         // True if tabs can't fit into available horizontal space.
         overflow: false,
       };
-    },
 
-    componentDidMount: function () {
+      this.onOverflow = this.onOverflow.bind(this);
+      this.onUnderflow = this.onUnderflow.bind(this);
+      this.onKeyDown = this.onKeyDown.bind(this);
+      this.onClickTab = this.onClickTab.bind(this);
+      this.setActive = this.setActive.bind(this);
+      this.renderMenuItems = this.renderMenuItems.bind(this);
+      this.renderPanels = this.renderPanels.bind(this);
+    }
+
+    componentDidMount() {
       let node = findDOMNode(this);
       node.addEventListener("keydown", this.onKeyDown);
 
       // Register overflow listeners to manage visibility
       // of all-tabs-menu. This menu is displayed when there
       // is not enough h-space to render all tabs.
       // It allows the user to select a tab even if it's hidden.
       if (this.props.showAllTabsMenu) {
         node.addEventListener("overflow", this.onOverflow);
         node.addEventListener("underflow", this.onUnderflow);
       }
 
       let index = this.state.tabActive;
       if (this.props.onMount) {
         this.props.onMount(index);
       }
-    },
+    }
 
-    componentWillReceiveProps: function (nextProps) {
+    componentWillReceiveProps(nextProps) {
       let { children, tabActive } = nextProps;
 
       // Check type of 'tabActive' props to see if it's valid
       // (it's 0-based index).
       if (typeof tabActive === "number") {
         let panels = children.filter((panel) => panel);
 
         // Reset to index 0 if index overflows the range of panel array
@@ -118,47 +128,47 @@ define(function (require, exports, modul
         let created = [...this.state.created];
         created[tabActive] = true;
 
         this.setState({
           created,
           tabActive,
         });
       }
-    },
+    }
 
-    componentWillUnmount: function () {
+    componentWillUnmount() {
       let node = findDOMNode(this);
       node.removeEventListener("keydown", this.onKeyDown);
 
       if (this.props.showAllTabsMenu) {
         node.removeEventListener("overflow", this.onOverflow);
         node.removeEventListener("underflow", this.onUnderflow);
       }
-    },
+    }
 
     // DOM Events
 
-    onOverflow: function (event) {
+    onOverflow(event) {
       if (event.target.classList.contains("tabs-menu")) {
         this.setState({
           overflow: true
         });
       }
-    },
+    }
 
-    onUnderflow: function (event) {
+    onUnderflow(event) {
       if (event.target.classList.contains("tabs-menu")) {
         this.setState({
           overflow: false
         });
       }
-    },
+    }
 
-    onKeyDown: function (event) {
+    onKeyDown(event) {
       // Bail out if the focus isn't on a tab.
       if (!event.target.closest(".tabs-menu-item")) {
         return;
       }
 
       let tabActive = this.state.tabActive;
       let tabCount = this.props.children.length;
 
@@ -169,29 +179,29 @@ define(function (require, exports, modul
         case "ArrowLeft":
           tabActive = Math.max(0, tabActive - 1);
           break;
       }
 
       if (this.state.tabActive != tabActive) {
         this.setActive(tabActive);
       }
-    },
+    }
 
-    onClickTab: function (index, event) {
+    onClickTab(index, event) {
       this.setActive(index);
 
       if (event) {
         event.preventDefault();
       }
-    },
+    }
 
     // API
 
-    setActive: function (index) {
+    setActive(index) {
       let onAfterChange = this.props.onAfterChange;
       let onBeforeChange = this.props.onBeforeChange;
 
       if (onBeforeChange) {
         let cancel = onBeforeChange(index);
         if (cancel) {
           return;
         }
@@ -212,21 +222,21 @@ define(function (require, exports, modul
         if (selectedTab) {
           selectedTab.focus();
         }
 
         if (onAfterChange) {
           onAfterChange(index);
         }
       });
-    },
+    }
 
     // Rendering
 
-    renderMenuItems: function () {
+    renderMenuItems() {
       if (!this.props.children) {
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(this.props.children)) {
         this.props.children = [this.props.children];
       }
 
@@ -294,19 +304,19 @@ define(function (require, exports, modul
       return (
         DOM.nav({className: "tabs-navigation"},
           DOM.ul({className: "tabs-menu", role: "tablist"},
             tabs
           ),
           allTabsMenu
         )
       );
-    },
+    }
 
-    renderPanels: function () {
+    renderPanels() {
       let { children, renderOnlySelected } = this.props;
 
       if (!children) {
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(children)) {
         children = [children];
@@ -354,45 +364,45 @@ define(function (require, exports, modul
           );
         });
 
       return (
         DOM.div({className: "panels"},
           panels
         )
       );
-    },
+    }
 
-    render: function () {
+    render() {
       return (
         DOM.div({ className: ["tabs", this.props.className].join(" ") },
           this.renderMenuItems(),
           this.renderPanels()
         )
       );
-    },
-  });
+    }
+  }
 
   /**
    * Renders simple tab 'panel'.
    */
-  let Panel = React.createClass({
-    displayName: "Panel",
+  class Panel extends Component {
+    static get propTypes() {
+      return {
+        title: React.PropTypes.string.isRequired,
+        children: React.PropTypes.oneOfType([
+          React.PropTypes.array,
+          React.PropTypes.element
+        ]).isRequired
+      };
+    }
 
-    propTypes: {
-      title: React.PropTypes.string.isRequired,
-      children: React.PropTypes.oneOfType([
-        React.PropTypes.array,
-        React.PropTypes.element
-      ]).isRequired
-    },
-
-    render: function () {
+    render() {
       return DOM.div({className: "tab-panel"},
         this.props.children
       );
     }
-  });
+  }
 
   // Exports from this module
   exports.TabPanel = Panel;
   exports.Tabs = Tabs;
 });
--- a/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
@@ -21,18 +21,18 @@ Test all-tabs menu.
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
-    const React = browserRequire("devtools/client/shared/vendor/react");
-    const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
+    const { Component, createFactory, DOM } = browserRequire("devtools/client/shared/vendor/react");
+    const Tabbar = createFactory(browserRequire("devtools/client/shared/components/tabs/TabBar"));
 
     // Create container for the TabBar. Set smaller width
     // to ensure that tabs won't fit and the all-tabs menu
     // needs to appear.
     const tabBarBox = document.createElement("div");
     tabBarBox.style.width = "200px";
     tabBarBox.style.height = "200px";
     tabBarBox.style.border = "1px solid lightgray";
@@ -40,22 +40,24 @@ window.onload = Task.async(function* () 
 
     // Render the tab-bar.
     const tabbar = Tabbar({
       showAllTabsMenu: true,
     });
 
     const tabbarReact = ReactDOM.render(tabbar, tabBarBox);
 
+    class TabPanelClass extends Component {
+      render() {
+        return DOM.div({}, "content");
+      }
+    }
+
     // Test panel.
-    let TabPanel = React.createFactory(React.createClass({
-      render: function () {
-        return React.DOM.div({}, "content");
-      }
-    }));
+    let TabPanel = createFactory(TabPanelClass);
 
     // Create a few panels.
     yield addTabWithPanel(1);
     yield addTabWithPanel(2);
     yield addTabWithPanel(3);
     yield addTabWithPanel(4);
     yield addTabWithPanel(5);
 
--- a/devtools/client/shared/components/tree/LabelCell.js
+++ b/devtools/client/shared/components/tree/LabelCell.js
@@ -3,36 +3,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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
-  const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
-  const { td, span } = React.DOM;
-  const PropTypes = React.PropTypes;
+  const { Component, DOM: dom, PropTypes } =
+    require("devtools/client/shared/vendor/react");
 
   /**
    * Render the default cell used for toggle buttons
    */
-  let LabelCell = React.createClass({
-    displayName: "LabelCell",
-
+  class LabelCell extends Component {
     // See the TreeView component for details related
     // to the 'member' object.
-    propTypes: {
-      id: PropTypes.string.isRequired,
-      member: PropTypes.object.isRequired
-    },
+    static get propTypes() {
+      return {
+        id: PropTypes.string.isRequired,
+        member: PropTypes.object.isRequired
+      };
+    }
 
-    render: function () {
+    render() {
       let id = this.props.id;
       let member = this.props.member;
       let level = member.level || 0;
 
       // Compute indentation dynamically. The deeper the item is
       // inside the hierarchy, the bigger is the left padding.
       let rowStyle = {
         "paddingInlineStart": (level * 16) + "px",
@@ -44,30 +41,30 @@ define(function (require, exports, modul
       } else if (member.hasChildren) {
         iconClassList.push("theme-twisty");
       }
       if (member.open) {
         iconClassList.push("open");
       }
 
       return (
-        td({
+        dom.td({
           className: "treeLabelCell",
           key: "default",
           style: rowStyle,
           role: "presentation"},
-          span({
+          dom.span({
             className: iconClassList.join(" "),
             role: "presentation"
           }),
-          span({
+          dom.span({
             className: "treeLabel " + member.type + "Label",
             "aria-labelledby": id,
             "data-level": level
           }, member.name)
         )
       );
     }
-  });
+  }
 
   // Exports from this module
   module.exports = LabelCell;
 });
--- a/devtools/client/shared/components/tree/TreeCell.js
+++ b/devtools/client/shared/components/tree/TreeCell.js
@@ -3,80 +3,83 @@
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
+  const { Component, PropTypes } = React;
   const { input, span, td } = React.DOM;
-  const PropTypes = React.PropTypes;
 
   /**
    * This template represents a cell in TreeView row. It's rendered
    * using <td> element (the row is <tr> and the entire tree is <table>).
    */
-  let TreeCell = React.createClass({
-    displayName: "TreeCell",
-
+  class TreeCell extends Component {
     // See TreeView component for detailed property explanation.
-    propTypes: {
-      value: PropTypes.any,
-      decorator: PropTypes.object,
-      id: PropTypes.string.isRequired,
-      member: PropTypes.object.isRequired,
-      renderValue: PropTypes.func.isRequired,
-      enableInput: PropTypes.bool,
-    },
+    static get propTypes() {
+      return {
+        value: PropTypes.any,
+        decorator: PropTypes.object,
+        id: PropTypes.string.isRequired,
+        member: PropTypes.object.isRequired,
+        renderValue: PropTypes.func.isRequired,
+        enableInput: PropTypes.bool,
+      };
+    }
 
-    getInitialState: function () {
-      return {
+    constructor(props) {
+      super(props);
+
+      this.state = {
         inputEnabled: false,
       };
-    },
+
+      this.getCellClass = this.getCellClass.bind(this);
+      this.updateInputEnabled = this.updateInputEnabled.bind(this);
+    }
 
     /**
      * Optimize cell rendering. Rerender cell content only if
      * the value or expanded state changes.
      */
-    shouldComponentUpdate: function (nextProps, nextState) {
+    shouldComponentUpdate(nextProps, nextState) {
       return (this.props.value != nextProps.value) ||
         (this.state !== nextState) ||
         (this.props.member.open != nextProps.member.open);
-    },
+    }
 
-    getCellClass: function (object, id) {
+    getCellClass(object, id) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getCellClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getCellClass(object, id);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    updateInputEnabled: function (evt) {
+    updateInputEnabled(evt) {
       this.setState(Object.assign({}, this.state, {
         inputEnabled: evt.target.nodeName.toLowerCase() !== "input",
       }));
-    },
+    }
 
-    render: function () {
+    render() {
       let {
         member,
         id,
         value,
         decorator,
         renderValue,
         enableInput,
       } = this.props;
@@ -122,17 +125,17 @@ define(function (require, exports, modul
         td({
           className: classNames.join(" "),
           role: "presentation"
         },
           cellElement
         )
       );
     }
-  });
+  }
 
   // Default value rendering.
   let defaultRenderValue = props => {
     return (
       props.object + ""
     );
   };
 
--- a/devtools/client/shared/components/tree/TreeHeader.js
+++ b/devtools/client/shared/components/tree/TreeHeader.js
@@ -2,68 +2,70 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
   const React = require("devtools/client/shared/vendor/react");
-
-  // Shortcuts
+  const { Component, PropTypes } = React;
   const { thead, tr, td, div } = React.DOM;
-  const PropTypes = React.PropTypes;
 
   /**
    * This component is responsible for rendering tree header.
    * It's based on <thead> element.
    */
-  let TreeHeader = React.createClass({
-    displayName: "TreeHeader",
-
+  class TreeHeader extends Component {
     // See also TreeView component for detailed info about properties.
-    propTypes: {
-      // Custom tree decorator
-      decorator: PropTypes.object,
-      // True if the header should be visible
-      header: PropTypes.bool,
-      // Array with column definition
-      columns: PropTypes.array
-    },
+    static get propTypes() {
+      return {
+        // Custom tree decorator
+        decorator: PropTypes.object,
+        // True if the header should be visible
+        header: PropTypes.bool,
+        // Array with column definition
+        columns: PropTypes.array
+      };
+    }
 
-    getDefaultProps: function () {
+    static get defaultProps() {
       return {
         columns: [{
           id: "default"
         }]
       };
-    },
+    }
 
-    getHeaderClass: function (colId) {
+    constructor(props) {
+      super(props);
+      this.getHeaderClass = this.getHeaderClass.bind(this);
+    }
+
+    getHeaderClass(colId) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getHeaderClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getHeaderClass(colId);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    render: function () {
+    render() {
       let cells = [];
       let visible = this.props.header;
 
       // Render the rest of the columns (if any)
       this.props.columns.forEach(col => {
         let cellStyle = {
           "width": col.width ? col.width : "",
         };
@@ -92,13 +94,13 @@ define(function (require, exports, modul
         thead({
           role: "presentation"
         }, tr({
           className: visible ? "treeHeaderRow" : "",
           role: "presentation"
         }, cells))
       );
     }
-  });
+  }
 
   // Exports from this module
   module.exports = TreeHeader;
 });
--- a/devtools/client/shared/components/tree/TreeRow.js
+++ b/devtools/client/shared/components/tree/TreeRow.js
@@ -2,124 +2,126 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
   const React = require("devtools/client/shared/vendor/react");
-  const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+  const { Component, createFactory, PropTypes } = React;
+  const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
+  const { tr } = React.DOM;
 
   // Tree
-  const TreeCell = React.createFactory(require("./TreeCell"));
-  const LabelCell = React.createFactory(require("./LabelCell"));
+  const TreeCell = createFactory(require("./TreeCell"));
+  const LabelCell = createFactory(require("./LabelCell"));
 
   // Scroll
   const { scrollIntoViewIfNeeded } = require("devtools/client/shared/scroll");
 
-  // Shortcuts
-  const { tr } = React.DOM;
-  const PropTypes = React.PropTypes;
-
   /**
    * This template represents a node in TreeView component. It's rendered
    * using <tr> element (the entire tree is one big <table>).
    */
-  let TreeRow = React.createClass({
-    displayName: "TreeRow",
-
+  class TreeRow extends Component {
     // See TreeView component for more details about the props and
     // the 'member' object.
-    propTypes: {
-      member: PropTypes.shape({
-        object: PropTypes.obSject,
-        name: PropTypes.sring,
-        type: PropTypes.string.isRequired,
-        rowClass: PropTypes.string.isRequired,
-        level: PropTypes.number.isRequired,
-        hasChildren: PropTypes.bool,
-        value: PropTypes.any,
-        open: PropTypes.bool.isRequired,
-        path: PropTypes.string.isRequired,
-        hidden: PropTypes.bool,
-        selected: PropTypes.bool,
-      }),
-      decorator: PropTypes.object,
-      renderCell: PropTypes.object,
-      renderLabelCell: PropTypes.object,
-      columns: PropTypes.array.isRequired,
-      id: PropTypes.string.isRequired,
-      provider: PropTypes.object.isRequired,
-      onClick: PropTypes.func.isRequired,
-      onMouseOver: PropTypes.func,
-      onMouseOut: PropTypes.func
-    },
+    static get propTypes() {
+      return {
+        member: PropTypes.shape({
+          object: PropTypes.obSject,
+          name: PropTypes.sring,
+          type: PropTypes.string.isRequired,
+          rowClass: PropTypes.string.isRequired,
+          level: PropTypes.number.isRequired,
+          hasChildren: PropTypes.bool,
+          value: PropTypes.any,
+          open: PropTypes.bool.isRequired,
+          path: PropTypes.string.isRequired,
+          hidden: PropTypes.bool,
+          selected: PropTypes.bool,
+        }),
+        decorator: PropTypes.object,
+        renderCell: PropTypes.object,
+        renderLabelCell: PropTypes.object,
+        columns: PropTypes.array.isRequired,
+        id: PropTypes.string.isRequired,
+        provider: PropTypes.object.isRequired,
+        onClick: PropTypes.func.isRequired,
+        onMouseOver: PropTypes.func,
+        onMouseOut: PropTypes.func
+      };
+    }
+
+    constructor(props) {
+      super(props);
+      this.getRowClass = this.getRowClass.bind(this);
+    }
 
     componentWillReceiveProps(nextProps) {
       // I don't like accessing the underlying DOM elements directly,
       // but this optimization makes the filtering so damn fast!
       // The row doesn't have to be re-rendered, all we really need
       // to do is toggling a class name.
       // The important part is that DOM elements don't need to be
       // re-created when they should appear again.
       if (nextProps.member.hidden != this.props.member.hidden) {
-        let row = ReactDOM.findDOMNode(this);
+        let row = findDOMNode(this);
         row.classList.toggle("hidden");
       }
-    },
+    }
 
     /**
      * Optimize row rendering. If props are the same do not render.
      * This makes the rendering a lot faster!
      */
-    shouldComponentUpdate: function (nextProps) {
+    shouldComponentUpdate(nextProps) {
       let props = ["name", "open", "value", "loading", "selected", "hasChildren"];
       for (let p in props) {
         if (nextProps.member[props[p]] != this.props.member[props[p]]) {
           return true;
         }
       }
 
       return false;
-    },
+    }
 
-    componentDidUpdate: function () {
+    componentDidUpdate() {
       if (this.props.member.selected) {
-        let row = ReactDOM.findDOMNode(this);
+        let row = findDOMNode(this);
         // Because this is called asynchronously, context window might be
         // already gone.
         if (row.ownerDocument.defaultView) {
           scrollIntoViewIfNeeded(row);
         }
       }
-    },
+    }
 
-    getRowClass: function (object) {
+    getRowClass(object) {
       let decorator = this.props.decorator;
       if (!decorator || !decorator.getRowClass) {
         return [];
       }
 
       // Decorator can return a simple string or array of strings.
       let classNames = decorator.getRowClass(object);
       if (!classNames) {
         return [];
       }
 
       if (typeof classNames == "string") {
         classNames = [classNames];
       }
 
       return classNames;
-    },
+    }
 
-    render: function () {
+    render() {
       let member = this.props.member;
       let decorator = this.props.decorator;
       let props = {
         id: this.props.id,
         role: "treeitem",
         "aria-level": member.level,
         "aria-selected": !!member.selected,
         onClick: this.props.onClick,
@@ -193,17 +195,17 @@ define(function (require, exports, modul
         }
       });
 
       // Render tree row
       return (
         tr(props, cells)
       );
     }
-  });
+  }
 
   // Helpers
 
   let RenderCell = props => {
     return TreeCell(props);
   };
 
   let RenderLabelCell = props => {
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -2,27 +2,32 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
-  // ReactJS
-  const React = require("devtools/client/shared/vendor/react");
+  const { cloneElement, Component, createFactory, DOM: dom, PropTypes } =
+    require("devtools/client/shared/vendor/react");
 
   // Reps
   const { ObjectProvider } = require("./ObjectProvider");
-  const TreeRow = React.createFactory(require("./TreeRow"));
-  const TreeHeader = React.createFactory(require("./TreeHeader"));
+  const TreeRow = createFactory(require("./TreeRow"));
+  const TreeHeader = createFactory(require("./TreeHeader"));
 
-  // Shortcuts
-  const DOM = React.DOM;
-  const PropTypes = React.PropTypes;
+  const defaultProps = {
+    object: null,
+    renderRow: null,
+    provider: ObjectProvider,
+    expandedNodes: new Set(),
+    expandableStrings: true,
+    columns: []
+  };
 
   /**
    * This component represents a tree view with expandable/collapsible nodes.
    * The tree is rendered using <table> element where every node is represented
    * by <tr> element. The tree is one big table where nodes (rows) are properly
    * indented from the left to mimic hierarchical structure of the data.
    *
    * The tree can have arbitrary number of columns and so, might be use
@@ -48,137 +53,192 @@ define(function (require, exports, modul
    *   getCellClass: function(object, colId);
    *   getHeaderClass: function(colId);
    *   renderValue: function(object, colId);
    *   renderRow: function(object);
    *   renderCell: function(object, colId);
    *   renderLabelCell: function(object);
    * }
    */
-  let TreeView = React.createClass({
-    displayName: "TreeView",
-
+  class TreeView extends Component {
     // The only required property (not set by default) is the input data
     // object that is used to puputate the tree.
-    propTypes: {
-      // The input data object.
-      object: PropTypes.any,
-      className: PropTypes.string,
-      label: PropTypes.string,
-      // Data provider (see also the interface above)
-      provider: PropTypes.shape({
-        getChildren: PropTypes.func,
-        hasChildren: PropTypes.func,
-        getLabel: PropTypes.func,
-        getValue: PropTypes.func,
-        getKey: PropTypes.func,
-        getType: PropTypes.func,
-      }).isRequired,
-      // Tree decorator (see also the interface above)
-      decorator: PropTypes.shape({
-        getRowClass: PropTypes.func,
-        getCellClass: PropTypes.func,
-        getHeaderClass: PropTypes.func,
+    static get propTypes() {
+      return {
+        // The input data object.
+        object: PropTypes.any,
+        className: PropTypes.string,
+        label: PropTypes.string,
+        // Data provider (see also the interface above)
+        provider: PropTypes.shape({
+          getChildren: PropTypes.func,
+          hasChildren: PropTypes.func,
+          getLabel: PropTypes.func,
+          getValue: PropTypes.func,
+          getKey: PropTypes.func,
+          getType: PropTypes.func,
+        }).isRequired,
+        // Tree decorator (see also the interface above)
+        decorator: PropTypes.shape({
+          getRowClass: PropTypes.func,
+          getCellClass: PropTypes.func,
+          getHeaderClass: PropTypes.func,
+          renderValue: PropTypes.func,
+          renderRow: PropTypes.func,
+          renderCell: PropTypes.func,
+          renderLabelCell: PropTypes.func,
+        }),
+        // Custom tree row (node) renderer
+        renderRow: PropTypes.func,
+        // Custom cell renderer
+        renderCell: PropTypes.func,
+        // Custom value renderef
         renderValue: PropTypes.func,
-        renderRow: PropTypes.func,
-        renderCell: PropTypes.func,
+        // Custom tree label (including a toggle button) renderer
         renderLabelCell: PropTypes.func,
-      }),
-      // Custom tree row (node) renderer
-      renderRow: PropTypes.func,
-      // Custom cell renderer
-      renderCell: PropTypes.func,
-      // Custom value renderef
-      renderValue: PropTypes.func,
-      // Custom tree label (including a toggle button) renderer
-      renderLabelCell: PropTypes.func,
-      // Set of expanded nodes
-      expandedNodes: PropTypes.object,
-      // Custom filtering callback
-      onFilter: PropTypes.func,
-      // Custom sorting callback
-      onSort: PropTypes.func,
-      // A header is displayed if set to true
-      header: PropTypes.bool,
-      // Long string is expandable by a toggle button
-      expandableStrings: PropTypes.bool,
-      // Array of columns
-      columns: PropTypes.arrayOf(PropTypes.shape({
-        id: PropTypes.string.isRequired,
-        title: PropTypes.string,
-        width: PropTypes.string
-      }))
-    },
+        // Set of expanded nodes
+        expandedNodes: PropTypes.object,
+        // Custom filtering callback
+        onFilter: PropTypes.func,
+        // Custom sorting callback
+        onSort: PropTypes.func,
+        // A header is displayed if set to true
+        header: PropTypes.bool,
+        // Long string is expandable by a toggle button
+        expandableStrings: PropTypes.bool,
+        // Array of columns
+        columns: PropTypes.arrayOf(PropTypes.shape({
+          id: PropTypes.string.isRequired,
+          title: PropTypes.string,
+          width: PropTypes.string
+        }))
+      };
+    }
 
-    getDefaultProps: function () {
-      return {
-        object: null,
-        renderRow: null,
-        provider: ObjectProvider,
-        expandedNodes: new Set(),
-        expandableStrings: true,
-        columns: []
-      };
-    },
+    static get defaultProps() {
+      return defaultProps;
+    }
 
-    getInitialState: function () {
-      return {
-        expandedNodes: this.props.expandedNodes,
-        columns: ensureDefaultColumn(this.props.columns),
+    constructor(props) {
+      super(props);
+
+      this.state = {
+        expandedNodes: props.expandedNodes,
+        columns: ensureDefaultColumn(props.columns),
         selected: null
       };
-    },
 
-    componentWillReceiveProps: function (nextProps) {
+      this.toggle = this.toggle.bind(this);
+      this.isExpanded = this.isExpanded.bind(this);
+      this.onKeyDown = this.onKeyDown.bind(this);
+      this.onKeyUp = this.onKeyUp.bind(this);
+      this.onClickRow = this.onClickRow.bind(this);
+      this.getSelectedRow = this.getSelectedRow.bind(this);
+      this.selectRow = this.selectRow.bind(this);
+      this.isSelected = this.isSelected.bind(this);
+      this.onFilter = this.onFilter.bind(this);
+      this.onSort = this.onSort.bind(this);
+      this.getMembers = this.getMembers.bind(this);
+      this.renderRows = this.renderRows.bind(this);
+    }
+
+    componentWillReceiveProps(nextProps) {
       let { expandedNodes } = nextProps;
       this.setState(Object.assign({}, this.state, {
         expandedNodes,
       }));
-    },
+    }
 
-    componentDidUpdate: function () {
+    componentDidUpdate() {
       let selected = this.getSelectedRow(this.rows);
       if (!selected && this.rows.length > 0) {
         // TODO: Do better than just selecting the first row again. We want to
         // select (in order) previous, next or parent in case when selected
         // row is removed.
         this.selectRow(this.rows[0].props.member.path);
       }
-    },
+    }
+
+    static subPath(path, subKey) {
+      return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
+    }
+
+    /**
+     * Creates a set with the paths of the nodes that should be expanded by default
+     * according to the passed options.
+     * @param {Object} The root node of the tree.
+     * @param {Object} [optional] An object with the following optional parameters:
+     *   - maxLevel: nodes nested deeper than this level won't be expanded.
+     *   - maxNodes: maximum number of nodes that can be expanded. The traversal is
+           breadth-first, so expanding nodes nearer to the root will be preferred.
+           Sibling nodes will either be all expanded or none expanded.
+     * }
+     */
+    static getExpandedNodes(rootObj, { maxLevel = Infinity, maxNodes = Infinity } = {}) {
+      let expandedNodes = new Set();
+      let queue = [{
+        object: rootObj,
+        level: 1,
+        path: ""
+      }];
+      while (queue.length) {
+        let {object, level, path} = queue.shift();
+        if (Object(object) !== object) {
+          continue;
+        }
+        let keys = Object.keys(object);
+        if (expandedNodes.size + keys.length > maxNodes) {
+          // Avoid having children half expanded.
+          break;
+        }
+        for (let key of keys) {
+          let nodePath = TreeView.subPath(path, key);
+          expandedNodes.add(nodePath);
+          if (level < maxLevel) {
+            queue.push({
+              object: object[key],
+              level: level + 1,
+              path: nodePath
+            });
+          }
+        }
+      }
+      return expandedNodes;
+    }
 
     // Node expand/collapse
 
-    toggle: function (nodePath) {
+    toggle(nodePath) {
       let nodes = this.state.expandedNodes;
       if (this.isExpanded(nodePath)) {
         nodes.delete(nodePath);
       } else {
         nodes.add(nodePath);
       }
 
       // Compute new state and update the tree.
       this.setState(Object.assign({}, this.state, {
         expandedNodes: nodes
       }));
-    },
+    }
 
-    isExpanded: function (nodePath) {
+    isExpanded(nodePath) {
       return this.state.expandedNodes.has(nodePath);
-    },
+    }
 
     // Event Handlers
 
-    onKeyDown: function (event) {
+    onKeyDown(event) {
       if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
         event.key)) {
         event.preventDefault();
       }
-    },
+    }
 
-    onKeyUp: function (event) {
+    onKeyUp(event) {
       let row = this.getSelectedRow(this.rows);
       if (!row) {
         return;
       }
 
       let index = this.rows.indexOf(row);
       switch (event.key) {
         case "ArrowRight":
@@ -204,67 +264,67 @@ define(function (require, exports, modul
             this.selectRow(previousRow.props.member.path);
           }
           break;
         default:
           return;
       }
 
       event.preventDefault();
-    },
+    }
 
-    onClickRow: function (nodePath, event) {
+    onClickRow(nodePath, event) {
       event.stopPropagation();
       let cell = event.target.closest("td");
       if (cell && cell.classList.contains("treeLabelCell")) {
         this.toggle(nodePath);
       }
       this.selectRow(nodePath);
-    },
+    }
 
-    getSelectedRow: function (rows) {
+    getSelectedRow(rows) {
       if (!this.state.selected || rows.length === 0) {
         return null;
       }
       return rows.find(row => this.isSelected(row.props.member.path));
-    },
+    }
 
-    selectRow: function (nodePath) {
+    selectRow(nodePath) {
       this.setState(Object.assign({}, this.state, {
         selected: nodePath
       }));
-    },
+    }
 
-    isSelected: function (nodePath) {
+    isSelected(nodePath) {
       return nodePath === this.state.selected;
-    },
+    }
 
     // Filtering & Sorting
 
     /**
      * Filter out nodes that don't correspond to the current filter.
      * @return {Boolean} true if the node should be visible otherwise false.
      */
-    onFilter: function (object) {
+    onFilter(object) {
       let onFilter = this.props.onFilter;
       return onFilter ? onFilter(object) : true;
-    },
+    }
 
-    onSort: function (parent, children) {
+    onSort(parent, children) {
       let onSort = this.props.onSort;
       return onSort ? onSort(parent, children) : children;
-    },
+    }
 
     // Members
 
     /**
      * Return children node objects (so called 'members') for given
      * parent object.
      */
-    getMembers: function (parent, level, path) {
+    getMembers(parent, level, path) {
       // Strings don't have children. Note that 'long' strings are using
       // the expander icon (+/-) to display the entire original value,
       // but there are no child items.
       if (typeof parent == "string") {
         return [];
       }
 
       let { expandableStrings, provider } = this.props;
@@ -315,22 +375,22 @@ define(function (require, exports, modul
           // Node path
           path: nodePath,
           // True if the node is hidden (used for filtering)
           hidden: !this.onFilter(child),
           // True if the node is selected with keyboard
           selected: this.isSelected(nodePath)
         };
       });
-    },
+    }
 
     /**
      * Render tree rows/nodes.
      */
-    renderRows: function (parent, level = 0, path = "") {
+    renderRows(parent, level = 0, path = "") {
       let rows = [];
       let decorator = this.props.decorator;
       let renderRow = this.props.renderRow || TreeRow;
 
       // Get children for given parent node, iterate over them and render
       // a row for every one. Use row template (a component) from properties.
       // If the return value is non-array, the children are being loaded
       // asynchronously.
@@ -362,27 +422,27 @@ define(function (require, exports, modul
             member.path);
 
           // If children needs to be asynchronously fetched first,
           // set 'loading' property to the parent row. Otherwise
           // just append children rows to the array of all rows.
           if (!Array.isArray(childRows)) {
             let lastIndex = rows.length - 1;
             props.member.loading = true;
-            rows[lastIndex] = React.cloneElement(rows[lastIndex], props);
+            rows[lastIndex] = cloneElement(rows[lastIndex], props);
           } else {
             rows = rows.concat(childRows);
           }
         }
       });
 
       return rows;
-    },
+    }
 
-    render: function () {
+    render() {
       let root = this.props.object;
       let classNames = ["treeTable"];
       this.rows = [];
 
       // Use custom class name from props.
       let className = this.props.className;
       if (className) {
         classNames.push(...className.split(" "));
@@ -398,83 +458,34 @@ define(function (require, exports, modul
         rows = [];
       }
 
       let props = Object.assign({}, this.props, {
         columns: this.state.columns
       });
 
       return (
-        DOM.table({
+        dom.table({
           className: classNames.join(" "),
           role: "tree",
           tabIndex: 0,
           onKeyDown: this.onKeyDown,
           onKeyUp: this.onKeyUp,
           "aria-label": this.props.label || "",
           "aria-activedescendant": this.state.selected,
           cellPadding: 0,
           cellSpacing: 0},
           TreeHeader(props),
-          DOM.tbody({
+          dom.tbody({
             role: "presentation"
           }, rows)
         )
       );
     }
-  });
-
-  TreeView.subPath = function (path, subKey) {
-    return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
-  };
-
-  /**
-   * Creates a set with the paths of the nodes that should be expanded by default
-   * according to the passed options.
-   * @param {Object} The root node of the tree.
-   * @param {Object} [optional] An object with the following optional parameters:
-   *   - maxLevel: nodes nested deeper than this level won't be expanded.
-   *   - maxNodes: maximum number of nodes that can be expanded. The traversal is
-         breadth-first, so expanding nodes nearer to the root will be preferred.
-         Sibling nodes will either be all expanded or none expanded.
-   * }
-   */
-  TreeView.getExpandedNodes = function (rootObj,
-    { maxLevel = Infinity, maxNodes = Infinity } = {}
-  ) {
-    let expandedNodes = new Set();
-    let queue = [{
-      object: rootObj,
-      level: 1,
-      path: ""
-    }];
-    while (queue.length) {
-      let {object, level, path} = queue.shift();
-      if (Object(object) !== object) {
-        continue;
-      }
-      let keys = Object.keys(object);
-      if (expandedNodes.size + keys.length > maxNodes) {
-        // Avoid having children half expanded.
-        break;
-      }
-      for (let key of keys) {
-        let nodePath = TreeView.subPath(path, key);
-        expandedNodes.add(nodePath);
-        if (level < maxLevel) {
-          queue.push({
-            object: object[key],
-            level: level + 1,
-            path: nodePath
-          });
-        }
-      }
-    }
-    return expandedNodes;
-  };
+  }
 
   // Helpers
 
   /**
    * There should always be at least one column (the one with toggle buttons)
    * and this function ensures that it's true.
    */
   function ensureDefaultColumn(columns) {