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

AKX
  • 152,115
  • 15
  • 115
  • 172
nmnsharma007
  • 235
  • 3
  • 13

2 Answers2

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>
  );
};
AKX
  • 152,115
  • 15
  • 115
  • 172
  • 1
    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. – nmnsharma007 Nov 03 '20 at 12:55
  • "gives some error" helps more or less no one. See my other points - you shouldn't do fetching on render. – AKX Nov 03 '20 at 12:57
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>
    );
  }
}
  • 1
    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? – nmnsharma007 Nov 03 '20 at 13:43
  • 1
    `` 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](https://reactjs.org/docs/react-component.html) document. – Akanksha singh Nov 03 '20 at 14:00
  • 1
    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:) – nmnsharma007 Nov 03 '20 at 14:12
  • I'm glad, it helped! :) – Akanksha singh Nov 03 '20 at 15:46