javascriptreactjsalgoliareact-instantsearch

onChange and setState don't work together in my code


I am trying to build a search box. However, when I want to set the state when something is typed in, the box just simply doesn't let me type in anything.

This is the class which calls the component of search box

export default class Tagsearch extends Component {

  constructor(props) {
    super(props);

    this.state = {
      hitsDisplay:false,
      inputContent:"",
      tags:[]
    };
  }
  openDisplay = () => {
    console.log("open")
    // this.setState({ hitsDisplay: true })
  }

  closeDisplay = () => {
    console.log("close")
    // this.setState({ hitsDisplay: false })
  }

  render() {

    let inputTags = (
      this.state.tags.map((tag, i) => 
        <li key={i} style={styles.items}>
          {tag}
          <button onClick={() => this.handleRemoveItem(i)}>
            (x)
          </button>
        </li>
      )
    )

    let result = (
      <div className="container-fluid" id="results">

      </div>
    )

    if (this.state.hitsDisplay) {
      result = (
        <div className="container-fluid" id="results">
          <div className="rows">
            <MyHits handleSelect={this.handleSelect}/>
          </div>

          <div className="rows">
            <Pagination />
          </div>
        </div>
      )
    }

    return (
      <InstantSearch
        appId="*******"
        apiKey="***********************"
        indexName="tags"
      >
        {inputTags}
        <SearchBox  
          connectSearchBox={connectSearchBox}
          openDisplay={this.openDisplay}
          closeDisplay={this.closeDisplay}
        />
        {result}

      </InstantSearch>
    )
  }
}

The following is the search box component

const SearchBox = props => {

  let { 
    connectSearchBox,
    openDisplay,
    closeDisplay
  } = props;

  const CustomSearchBox = connectSearchBox(({ currentRefinement, refine }) => {
    const handleChange = (e, refine) => {
      refine(e.target.value)
      // console.log(e.target.value)
      if (e.target.value !== "") {
        openDisplay();
      } else {
        closeDisplay();
      }
    }

    return (
      <label>
        <ul style={styles.container}>
          <input
            style={styles.input}
            type="text"
            value={currentRefinement}
            onChange={e => handleChange(e, refine)}
          />
        </ul>
      </label>
    )
  })

  return (
    <CustomSearchBox />
  )
}

export default SearchBox;

If I comment the two setStates in open & closeDisplay, It work fine, it prints out open and close accordingly. However, once I enable setState, the input box simply doesn't allow me to type in anything.

Any help is appreciated.


Solution

  • Your code is incorrectly written. connectSearchBox is meant to wire up a component to the Algolia api. It's a one time setup on a component definition. It returns a higher order component that wraps the given component with api features. You can see the source code here. By putting your custom SearchBox inside the SearchBox function, you are causing the component to be rebuilt and reconnected on each render() cycle, thereby not preserving state. This is why as soon as you setState your search text disappears.

    import { connectSearchBox } from 'react-instantsearch-dom';
    
    const CustomSearchBox = ({ currentRefinement, refine, openDisplay, closeDisplay, ...props }) => {
        const handleChange = (e, refine) => {
          refine(e.target.value)
          if (e.target.value !== "")
            openDisplay();
          else
            closeDisplay();
        }
    
        return (
          <label>
            <ul style={styles.container}>
              <input
                style={styles.input}
                type="text"
                value={currentRefinement}
                onChange={e => handleChange(e, refine)}
              />
            </ul>
          </label>
        )
      })
    
    export default connectSearchBox(CustomSearchBox);
    

    Usage

    import CustomSearchBox from './CustomSearchBox'
    ...
           <CustomSearchBox  
              openDisplay={this.openDisplay}
              closeDisplay={this.closeDisplay}
            />
    

    Usage example from the docs. I think what you were trying to achieve was to pass props down to your component but the connectSearchBox already guarantees that any props you pass to the HOC also gets passed down to the your custom search box. Line 335