Bug 1535077 - Switch to using Ref and PureComponent in the SearchBox component. r=Honza
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 13 Mar 2019 14:31:54 -0400
changeset 521966 abe66ccddd970628f025780e1e5d0adc0b06e018
parent 521903 d55401632cea92b6b2775ba278274b5490275876
child 521967 0cc6396199a8508d711e080bee5cd16029748f29
push id10870
push usernbeleuzu@mozilla.com
push dateFri, 15 Mar 2019 20:00:07 +0000
treeherdermozilla-beta@c594aee5b7a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1535077
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1535077 - Switch to using Ref and PureComponent in the SearchBox component. r=Honza Differential Revision: https://phabricator.services.mozilla.com/D23366
devtools/client/shared/components/SearchBox.js
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -1,89 +1,95 @@
 /* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
+const { createFactory, createRef, PureComponent } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
-const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
-const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
 
+loader.lazyGetter(this, "AutocompletePopup", function() {
+  return createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
+});
 loader.lazyGetter(this, "MDNLink", function() {
   return createFactory(require("./MdnLink"));
 });
 
-class SearchBox extends Component {
+loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
+
+class SearchBox extends PureComponent {
   static get propTypes() {
     return {
+      autocompleteProvider: PropTypes.func,
       delay: PropTypes.number,
       keyShortcut: PropTypes.string,
-      onChange: PropTypes.func,
+      learnMoreTitle: PropTypes.string,
+      learnMoreUrl: PropTypes.string,
+      onBlur: PropTypes.func,
+      onChange: PropTypes.func.isRequired,
       onFocus: PropTypes.func,
-      onBlur: PropTypes.func,
       onKeyDown: PropTypes.func,
-      placeholder: PropTypes.string,
+      placeholder: PropTypes.string.isRequired,
       plainStyle: PropTypes.bool,
-      type: PropTypes.string,
-      autocompleteProvider: PropTypes.func,
-      learnMoreUrl: PropTypes.string,
-      learnMoreTitle: PropTypes.string,
+      type: PropTypes.string.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       value: "",
       focused: false,
     };
 
+    this.autocompleteRef = createRef();
+    this.inputRef = createRef();
+
+    this.onBlur = this.onBlur.bind(this);
     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, event => {
       event.preventDefault();
-      this.refs.input.focus();
+      this.inputRef.current.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) {
+    if (this.state.value !== this.inputRef.current.value) {
       this.setState({
         focused: true,
-        value: this.refs.input.value,
+        value: this.inputRef.current.value,
       });
     }
 
     if (!this.props.delay) {
       this.props.onChange(this.state.value);
       return;
     }
 
@@ -96,17 +102,17 @@ class SearchBox extends Component {
     // 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.setState({ value: "" });
     this.onChange();
   }
 
   onFocus() {
     if (this.props.onFocus) {
       this.props.onFocus();
     }
 
@@ -121,17 +127,17 @@ class SearchBox extends Component {
     this.setState({ focused: false });
   }
 
   onKeyDown(e) {
     if (this.props.onKeyDown) {
       this.props.onKeyDown();
     }
 
-    const { autocomplete } = this.refs;
+    const autocomplete = this.autocompleteRef.current;
     if (!autocomplete || autocomplete.state.list.length <= 0) {
       return;
     }
 
     switch (e.key) {
       case "ArrowDown":
         autocomplete.jumpBy(1);
         break;
@@ -159,63 +165,62 @@ class SearchBox extends Component {
       case "End":
         autocomplete.jumpToBottom();
         break;
     }
   }
 
   render() {
     let {
-      type = "search",
+      autocompleteProvider,
+      learnMoreTitle,
+      learnMoreUrl,
       placeholder,
-      autocompleteProvider,
       plainStyle,
-      learnMoreUrl,
-      learnMoreTitle,
+      type = "search",
     } = this.props;
     const { value } = this.state;
-    const divClassList = ["devtools-searchbox", "has-clear-btn"];
+    const showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
+
     const inputClassList = [`devtools-${type}input`];
     if (plainStyle) {
       inputClassList.push("devtools-plaininput");
     }
-    const showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
-
     if (value !== "") {
       inputClassList.push("filled");
       learnMoreUrl = false;
     }
 
     return dom.div(
-      { className: divClassList.join(" ") },
+      { className: "devtools-searchbox has-clear-btn" },
       dom.input({
         className: inputClassList.join(" "),
+        onBlur: this.onBlur,
         onChange: this.onChange,
         onFocus: this.onFocus,
-        onBlur: this.onBlur,
         onKeyDown: this.onKeyDown,
         placeholder,
-        ref: "input",
+        ref: this.inputRef,
         value,
       }),
       dom.button({
         className: "devtools-searchinput-clear",
-        hidden: value == "",
+        hidden: value === "",
         onClick: this.onClearButtonClick,
       }),
       learnMoreUrl && MDNLink({
+        title: learnMoreTitle,
         url: learnMoreUrl,
-        title: learnMoreTitle,
       }),
       showAutocomplete && AutocompletePopup({
         autocompleteProvider,
         filter: value,
-        ref: "autocomplete",
         onItemSelected: (itemValue) => {
           this.setState({ value: itemValue });
           this.onChange();
         },
+        ref: this.autocompleteRef,
       })
     );
   }
 }
 
 module.exports = SearchBox;