nmnsharma007
nmnsharma007

Reputation: 236

handling user input in react to make an API call

All i want is that through App.js, I take input for city name and display the AQI fetched from the API in AirQuality.js. But it just isn't working correctly. Can someone please help me on this? I am new to react API.It works fine if I simply hardcode the city name.

import React from "react";
import "./App.css";

class Airquality extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      info: {},
      city: {},
      parameters: {},
      isLoaded: false,
    };
  }

  getAirquality = (search) => {
    fetch(
      "https://api.waqi.info/feed/" +
        search +
        "/?token=3c8f71c8438c1b6a06f60477eaf429fe2b61cd3d",
    )
      .then((response) => response.json())
      .then((data) => {
        const newInfo = data.data;
        const newCity = newInfo.city;
        const tempData = newInfo.iaqi;
        const newParameters = tempData.pm25;
        const loaded = true;
        const newState = Object.assign({}, this.state, {
          isLoaded: loaded,
          info: newInfo,
          city: newCity,
          parameters: newParameters,
        });
        this.setState(newState);
      });
  };

  componentDidMount() {
    this.getAirquality(this.props.search);
  }

  render() {
    this.getAirquality(this.props.search);
    if (!this.state.isLoaded) {
      return <div>....Loading</div>;
    } else {
      return (
        <div className="App">
          <h1>
            The AQI(Air Quality Index) in{" "}
            {this.state.city.name} is {this.state.info.aqi}{" "}
            today.
            <br />
            Concentration of PM2.5 particle :{" "}
            {this.state.parameters.v}
          </h1>
        </div>
      );
    }
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: "" };
    this.handleInput = this.handleInput.bind(this);
  }
  handleInput(event) {
    this.setState({
      name: event.target.value,
    });
  }

  render() {
    return (
      <div className="App">
        <form onSubmit={this.handleInput}>
          <label>
            Enter the city name:
            <input
              type="text"
              onChange={this.handleInput}
            />
          </label>
        </form>
        <Airquality search={this.state.value} />
      </div>
    );
  }
}

Upvotes: 3

Views: 3785

Answers (2)

Akanksha singh
Akanksha singh

Reputation: 151

Your code has some issues. I'm pasting working code below.

  1. Use different handlers for input change handleInput and form submit handleFormSubmit. Using same handler creates confusion and both have different purposes. I recommend using different handlers.
  2. In <Airquality search={this.state.name} /> value of search props should be this.state.name.
  3. In Form submit handler, use event.preventDefault() to avoid reloading the page. Refer this stackoverflow answer for more context.
  4. As mentioned by @AKX you shouldn't call fetch functions on render. As you are calling this.getAirquality(this.props.search) inside render and this function is setting the state hence render is being called again which turns into infinite loops. Instead, use componentDidMount in class components, or useEffect in function components.
import React from "react";
import "./App.css";

class Airquality extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      info: {},
      city: {},
      parameters: {},
      isLoaded: false,
    };
  }

  getAirquality (search) {
    fetch(
      "https://api.waqi.info/feed/" +
      search +
      "/?token=3c8f71c8438c1b6a06f60477eaf429fe2b61cd3d",
    )
      .then((response) => response.json())
      .then((data) => {
        const newInfo = data.data;
        const newCity = newInfo.city;
        const tempData = newInfo.iaqi;
        const newParameters = tempData.pm25;
        const loaded = true;
        const newState = Object.assign({}, this.state, {
          isLoaded: loaded,
          info: newInfo,
          city: newCity,
          parameters: newParameters,
        });
        this.setState(newState);
      });
  };

  componentDidMount () {
    this.getAirquality(this.props.search);
  }

  render () {
    if (!this.state.isLoaded) {
      return <div>....Loading</div>;
    } else {
      return (
        <div className="App">
          <h1>
            The AQI(Air Quality Index) in{" "}
            {this.state.city.name} is {this.state.info.aqi}{" "}
            today.
            <br />
            Concentration of PM2.5 particle :{" "}
            {this.state.parameters.v}
          </h1>
        </div>
      );
    }
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: "", formSubmit: false };
    this.handleInput = this.handleInput.bind(this);
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
  }

  handleInput (event) {
    console.log(event.target.value)
    this.setState({
      name: event.target.value,
    });
  }

  handleFormSubmit (event) {
    event.preventDefault()
    console.log(this.state.name)
    this.setState({formSubmit: true})
  }

  render () {
    return (
      <div className="App">
        <form onSubmit={this.handleFormSubmit}>
          <label>
            Enter the city name:
            <input
              type="text"
              onChange={this.handleInput}
            />
          </label>
        </form>
        {this.state.formSubmit && <Airquality search={this.state.name} />}
      </div>
    );
  }
}

Upvotes: 1

AKX
AKX

Reputation: 169298

You're using search={this.state.value} where the correct name of the state value is search={this.state.name}. That's why you always get undefined in the child component.

Then, some other points:

  • You're missing value={this.state.name} from the <input>. This makes your input "semi-controlled" and may lead to weirdness. (The browser console should be warning you about this.)
  • You shouldn't call fetch functions on render; instead, use componentDidUpdate in class components, or useEffect in function components.

Here's how I'd implement this with function components and without external libraries. (With external libraries allowed, I'd use swr for fetching data without race conditions.

import React from "react";
import "./App.css";

const Airquality = ({ search }) => {
  const [isLoaded, setIsLoaded] = React.useState(false);
  const [info, setInfo] = React.useState({});
  React.useEffect(() => {
    setIsLoaded(false);
    fetch(
      "https://api.waqi.info/feed/" +
        search +
        "/?token=3c8f71c8438c1b6a06f60477eaf429fe2b61cd3d",
    )
      .then((response) => response.json())
      .then((response) => {
        const {data} = response;
        const {city, aqi} = data;
        setInfo({
          city,
          pm25: data.iaqi.pm25.v,
          aqi,
          data,
        });
        setIsLoaded(true);
      });
  }, [search]);

  if (!isLoaded) {
    return <div>....Loading</div>;
  }
  const {city, pm25, aqi} = info;
  return (
      <div>
        The AQI(Air Quality Index) in {city} is {aqi} today.
        <br />
        Concentration of PM2.5 particle : {pm25}
      </div>
  );
};

const App = () => {
  const [name, setName] = React.useState("");
  return (
    <div className="App">
      <form onSubmit={this.handleInput}>
        <label>
          Enter the city name:
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </label>
      </form>
      <Airquality search={this.state.value} />
    </div>
  );
};

Upvotes: 1

Related Questions