3

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>
    );
  }
}

2 Answers 2

1

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>
    );
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

The code you sent works only if I hard code the name. How do you fetch with the passed name? Also, you used componentDidMount and @AKX suggested to use componentDidUpdate. I wonder what's better and why?
<Airquality search={this.state.name} /> is not getting rendered on form submit, we should only render this component on form submit. I've updated the code. Please check and let me know if it works. To understand react lifecycle methods, please go through this document.
Finally worked. I still need to go a long long way to get a hang of this. But thanks a lot. At least I understood my mistakes. Thank you:)
I'm glad, it helped! :)
1

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>
  );
};

2 Comments

There are still couple of problems.As soon as I type a letter, it reloads and gives some error about fetch failing. Also, I tried printing to console the fetched object to check, but error again. If I hard code the name, it shows the result on browser but console keeps printing forever.
"gives some error" helps more or less no one. See my other points - you shouldn't do fetching on render.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.