I used two 'setState' inside a handleChange of input, for searching on the array. why the search is unseccesfull?

Viewed 94

I have an array of the names of cities. also, I have an input that I want the any input value to be searched on the array, and synchronously show the input's placeholder according to city's name that started with input value, whenever that user typing a character.

a part of cities.json:

[ "Aberdeen", "Abilene", "Akron", "Albany", "Albuquerque", "Alexandria", "Allentown", "Amarillo", "Anaheim", "Anchorage", "Ann Arbor", "Antioch", "Apple Valley", "Appleton", "Arlington", "Arvada", "Asheville", "Athens", "Atlanta", "Atlantic City", "Augusta", "Aurora", "Austin", "Bakersfield", "Baltimore", "Barnstable", "Baton Rouge", "Beaumont", . . . . . . . . . . . . . . . "Wilmington", "Winston", "Winter Haven", "Worcester", "Yakima", "Yonkers", "York", "Youngstown" ]

index.jsx:

import React from "react";
import ReactDOM from "react-dom";
import cities from "./cities.json"; //cities is an array.

class App extends React.Component{
    constructor(props) {
        super(props);
        this.state = {
            value: '',
            placeholder: ''
        }
    }

    handleChange = (e) => {
        this.setState({
            value: e.target.value
        });
        for (let i = 0; i < cities.length; i++){
            if (cities[i].startsWith(`${this.state.value}`)){
                return this.setState({
                    placeholder: cities[i]
                });
            }
        }
    }


    render() {
        return (
            <div>
                <input value={this.state.value} placeholder={this.state.placeholder} onChange={this.handleChange}/>
            </div>
        );
    }
}

ReactDOM.render(<App/>, document.getElementById("root"));

But two issues:

1- Use two setState: Do second setState use result of first setState?

2- Output is not expected: typed value is not synchronous with placeholder value. placeholder is shown when the input is cleared(while the placeholder must be empty,too). also it only shows first city, with any starter letter. e.g if I type "Akro" or "Alba", it shows "Aberdeen" instead "Akron" and "Albany", as placeholder. as well as if I type "Baton", it shows "Bakersfield" instead "Baton Rouge"(note the array).

my output:

enter image description here enter image description here

expected output:

enter image description here

What is your solution? :)

2 Answers

the setState function is async, so if you want to use the newly state you have to provide a callback to setState and use the new state inside it. So, for example, in your case you have to do something like:

handleChange = (e) => {
    this.setState(
      {
        value: e.target.value
      },
      () => {
        if (this.state.value) {
            for (let i = 0; i < cities.length; i++) {
                if (cities[i].startsWith(this.state.value)) {
                    return this.setState({
                        placeholder: cities[i]
                    });
                }
            }
        }
        return this.setState({ placeholder: ''})
      }
    );
  };

In order to display the placeholder correctly you need to do the following changes inside the render:

render() {
    return (
      <div style={{ position: "relative" }}>
        <input value={this.state.value} style={{ fontFamily: 'Arial', fontSize: 14 }} onChange={this.handleChange} />
        <span style={{ position: "absolute", top: 0, left: 0, padding: '3px 4px', fontSize: 14, fontFamily: 'Arial', opacity: 0.5 }}>
          {this.state.placeholder}
        </span>
      </div>
    );
  }

@Marco Nisi is correct but according to your question you want the saw city suggestions. @Arman placeholder is only displayed when there is no value in the input box. but you can use the datalist provided in the html. Please find the link to the working solution here.

import React from "react";
import cities from "./cities.json"; //cities is an array.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: "",
      placeholder: "",
      suggestions: []
    };
  }

  handleChange = (e) => {
    this.setState(
      {
    value: e.target.value
      },
      () => {
    this.setState(
      {
        suggestions: []
      },
      () => {
        for (let i = 0; i < cities.length; i++) {
          if (cities[i].startsWith(`${this.state.value}`)) {
            this.setState({
              suggestions: [...this.state.suggestions, cities[i]]
            });
          }
        }
      }
    );
      }
    );
  };

  render() {
    return (
      <div>
    <label htmlFor="city">Choose your city:</label>
    <input
      value={this.state.value}
      placeholder={this.state.placeholder}
      onChange={this.handleChange}
      list="suggested_cities"
      name="city"
      id="city"
    />
    <datalist id="suggested_cities">
      {this.state.suggestions.map((suggestion) => {
        return <option value={suggestion} key={suggestion} />;
      })}
    </datalist>
      </div>
    );
  }
}

export default App;

Link to the fiddle for working example. https://codesandbox.io/s/react-fiddle-forked-viqmt?from-embed

Related