Bug 1412311 - DevTools Shared Components to ES6 classes r?nchevobbe draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 27 Oct 2017 15:33:10 +0100
changeset 690127 283c68b24f1b93bdbbe7f076074cb62dd20b1078
parent 690034 cd7217cf05a2332a8fd7b498767a07b2c31ea657
child 738501 196050278614052d2d643c679f4215dd41be3eb4
push id87224
push userbmo:mratcliffe@mozilla.com
push dateWed, 01 Nov 2017 17:52:27 +0000
reviewersnchevobbe
bugs1412311, 1413167
milestone58.0a1
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) {