iteyran
iteyran

Reputation: 135

Input doesn't clear content even after state is modified in React

I have an input field with a "close (x)" icon next to it. I am handling state as the value in the input field changes, and once the user hits Enter I clear the value in the state. However, after the re-rendering (because of state change) the previous input still remains there in the input field.

constructor(props) {
    super(props);
    this.state = {
      filterSearchQuery: null, filterSearch: null}

    this.handleSearchInput = this.handleSearchInput.bind(this);
    this.handleSearchReset = this.handleSearchReset.bind(this);
}

handleSearchInput(e) {
     //   e.preventDefault();
    let val = e.target.value;
    if (e.keyCode == 13)
      this.setState({ entries: [], filterSearchQuery: null, filterSearch: val, pageIndex: 0, hasMore: false }, this.loadEntries);
    else
      this.setState({ filterSearchQuery: val });
}

handleSearchReset(e) {
    //e.preventDefault();
    this.setState({ entries: [], pageIndex: 0, hasMore: false, filterSearchQuery: null, filterSearch: null }, this.loadEntries);
}

render and return :

<input type="text" className="outline-none flex-fill pt-0_2em pb-0_2em border-white border-none bg-color-white color-black font-12px font-weight-bold opacity-0_9" value={this.state.filterSearchQuery} placeholder="Search in results." onKeyUp={(e) => this.handleSearchInput(e)} onChange={(e) => this.handleSearchInput(e)} autoFocus={true} />
{
    (this.state.filterSearchQuery && this.state.filterSearchQuery.length > 0) &&
    <div className="">
    <button type="button" className="close" aria-label="Close" onClick={this.handleSearchReset}>
        <span aria-hidden="true">&times;</span>
    </button>
 </div>
}

The handleSearchReset function works and sets the state as expected. Both the filterSearch and filterSearchQuery values are set properly. However, after re-rendering the filterSearchQuery value still is visible in the input field. How can I overcome this or where am I missing?

Upvotes: 3

Views: 1969

Answers (1)

ggorlen
ggorlen

Reputation: 56925

Since you're using controlled components, simply setting them to null isn't sufficient to perform a clear. Instead, set them to the empty string "". Here's a minimal, complete example:

class Search extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterSearchQuery: "",
      filterSearch: ""
    };
    this.handleSearchInput = this.handleSearchInput.bind(this);
    this.handleSearchReset = this.handleSearchReset.bind(this);
  }

  handleSearchInput(e) {
    if (e.key === "Enter") {
      this.setState(prevState => ({
        filterSearchQuery: "",
        filterSearch: prevState.filterSearchQuery,
      }));
    }
    else {
      const {value} = e.target;
      this.setState(prevState => ({
        ...prevState,
        filterSearchQuery: value,
      }));
    }
  }

  handleSearchReset(e) {
    this.setState(prevState => ({
      filterSearchQuery: "",
      filterSearch: "",
    }));
  }

  render() {
    const {filterSearchQuery, filterSearch} = this.state;
    return (
      <div>
        <input 
          value={filterSearchQuery} 
          onKeyUp={this.handleSearchInput} 
          onChange={this.handleSearchInput} 
        />
        {filterSearchQuery &&
          <div>
            <button onClick={this.handleSearchReset}>
              <span>&times;</span>
            </button>
          </div>
        }
        {filterSearch &&
          <div>
            ...testing search for '{filterSearch}'...
          </div>
        }
      </div>
    );
  }
}

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<Search />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

Hooks version:

const Search = () => {
  const [
    filterSearchQuery,
    setFilterSearchQuery
  ] = React.useState("");
  const [filterSearch, setFilterSearch] = React.useState("");

  const handleSearchInput = e => {
    if (e.key === "Enter") {
      setFilterSearch(filterSearchQuery);
      setFilterSearchQuery("");
    }
    else {
      const {value} = e.target;
      setFilterSearchQuery(value);
    }
  };

  const handleSearchReset = e => {
    setFilterSearch("");
    setFilterSearchQuery("");
  };

  return (
    <div>
      <input
        value={filterSearchQuery} 
        onKeyUp={handleSearchInput} 
        onChange={handleSearchInput} 
      />
      {filterSearchQuery &&
        <div>
          <button onClick={handleSearchReset}>
            <span>&times;</span>
          </button>
        </div>
      }
      {filterSearch &&
        <div>
          ...testing search for '{filterSearch}'...
        </div>
      }
    </div>
  );
};

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<Search />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

Minor remarks:

  • event.keyCode is deprecated. Use .key instead.
  • Since you already bound this to this.handleSearchFoo in the constructor, there's no need for an arrow function wrapper in render().
  • It's a good idea to use braces around all conditionals to improve readability and avoid subtle bugs.
  • Using destructuring can help clean up verbosity.
  • Avoid long horizontal lines to improve readability.
  • For strings, this.state.filterSearchQuery && this.state.filterSearchQuery.length > 0 can be simply this.state.filterSearchQuery because unlike empty arrays, empty strings are falsey.
  • Always use === rather than == in JS.

Upvotes: 4

Related Questions