-2

Well I am making this weather app with the use of an api. So, first I have to show the weather of a default city. Then when I select the other cities, the component will render again to give me the weather data of selected cities. So far I have done on my project:

const initialDivisionId = "3";

const getDivision = (divisionId) => {
  return divisions.find((division) => division.id === divisionId);
};

class Weather extends React.Component {
  constructor(props) {
    super(props);
    const division = getDivision(initialDivisionId);
    this.state = {
      divisionId: initialDivisionId,
      lat: division.lat,
      long: division.long,
      currentWeatherData: [],
      hourlyWeatherData: [],
      dailyWeatherData: []
    };
  }

  onDivisionChange = (event) => {
    const divisionId = event.target.value;
    const { lat, long } = getDivision(divisionId);

    this.setState({
      divisionId: event.target.value,
      lat: lat.lat,
      long: long.long
    });
    
  };

  componentDidMount() {
    fetch(
      `https://api.openweathermap.org/data/2.5/onecall?lat=${this.state.lat}&lon=${this.state.long}&units=metric&exclude=alerts&appid={api_key}`
    )
      .then((res) => res.json())
      .then(
        (result) => {
          this.setState({
            currentWeatherData: result.current,
            hourlyWeatherData: result.hourly[0],
            dailyWeatherData: result.daily[0]
          });
        },
        (error) => {
          console.log(error);
          this.setState({
            error
          });
        }
      );
  }

  render() {
    console.log(this.state.currentWeatherData);
    console.log(this.state.hourlyWeatherData);
    console.log(this.state.dailyWeatherData);
    return (
      <div>
        <Title />

        <Select
          variant="filled"
          w="30%"
          placeholder="Select option"
          value={this.state.divisionId}
          onChange={this.onDivisionChange}
        >
          {divisions.map((division) => (
            <option key={division.id} value={division.id}>
              {division.name}
            </option>
          ))}
        </Select>
        <div>
          <Stack spacing={6}>
            <Heading color="tomato" size="4xl">
              {this.state.currentWeatherData.dt}
            </Heading>

            <Heading color="gray" size="3xl">
              {this.state.currentWeatherData.temp}
            </Heading>

            <Heading color="blue" size="2xl">
              {this.state.hourlyWeatherData.pressure}
            </Heading>

            <Heading color="black" size="xl">
              {this.state.hourlyWeatherData.temp}
            </Heading>

            <Heading color="black" size="lg">
              {this.state.dailyWeatherData.clouds}
            </Heading>

            <Heading color="yellow" size="md">
              {this.state.dailyWeatherData.humidity}
            </Heading>
          </Stack>
        </div>
      </div>
    );
  }
}

const Title = () => {
  return (
    <Text align="center">
      <Heading size="xl">Weather App</Heading>
    </Text>
  );
};

function App() {
  return (
    <ChakraProvider>
      <Weather />
    </ChakraProvider>
  );
}

export default App;

So, I know I have to use shouldComponentUpdate lifecycle method if I want to re-render. How can I re-render the same component if I want to have the weather response of other cities? Or do I need to pass states as props to other component and then I have to fetch the api? Need help!

tanjim anim
  • 337
  • 3
  • 23
  • If you set state, the component should re-render without you having to do anything else to it. Have you tried just setting state with the new city info? – Nick Dec 28 '20 at 10:25
  • what does that mean? whenever there will be change in state your component will re-render.. – sourav satyam Dec 28 '20 at 10:26
  • Every time you call `this.setState` the component will re-render, if a DOM change should take place according to the data which was changed. – vsync Dec 28 '20 at 10:27
  • Ah I see your issue, you have your API call only in `componentDidMount`, so it'll only fetch data when your component mounts. What you'll want to do is also have it in `componentDidUpdate`. To try to avoid redundant code, you can put it in its own method. – Nick Dec 28 '20 at 10:27
  • @Nick okay so the fetch should be inside the componentDidmount? – tanjim anim Dec 28 '20 at 10:28

1 Answers1

3

The issue you're hitting isn't that you need to re-render your component, it's that you need to hit the weather API when your state updates. You can do this by making sure you call your API in the componentDidUpdate lifecycle method. Here is some updated code that does that (and abstracts the API call to avoid redundant code).

fetchWeatherData() {
  fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${this.state.lat}&lon=${this.state.long}&units=metric&exclude=alerts&appid={api_key}`
  )
    .then((res) => res.json())
    .then(
      (result) => {
        this.setState({
          currentWeatherData: result.current,
          hourlyWeatherData: result.hourly[0],
          dailyWeatherData: result.daily[0]
        });
      },
      (error) => {
        console.log(error);
        this.setState({
          error
        });
      }
    );
}

componentDidMount() {
  this.fetchWeatherData();
}

componentDidUpdate(_, prevState) {
  if (prevState.lat !== this.state.lat || prevState.long !== this.state.long) {
    this.fetchWeatherData();
  }
}

In the constructor, make sure to bind fetchWeatherData to this.

constructor(props) {
  // Existing constructor code here
  this.fetchWeatherData = this.fetchWeatherData.bind(this);
}
Nick
  • 16,066
  • 3
  • 16
  • 32
  • it throws a ```Unhandled Runtime Error TypeError: Cannot read property '0' of undefined```. – tanjim anim Dec 28 '20 at 10:39
  • You might have to do some troubleshooting inside the return of the fetch call. That error tells me there's either no `result.hourly` or `result.daily` in the return from your fetch call at some point. Is there a different type of return from the API if, for example, you provide it a bad lat/lon? – Nick Dec 28 '20 at 10:41
  • (Also make sure the API key is included correctly. Right now I copied it how you had it in the question, but it looks like it should be a template literal variable `${api_key}` instead of what it currently is (without the dollar sign) – Nick Dec 28 '20 at 10:43
  • I obviously entered the right api key. But I think there might be some issues with the response when I select the option. Have to work on that. – tanjim anim Dec 28 '20 at 10:45
  • Your code here makes an infinite loop, took my friend to notice it. – tanjim anim Dec 28 '20 at 14:35
  • Check out the docs here, you can use the second argument of componentDidUpdate to check it either lat or long are different than before. Otherwise, don’t fetch the data. https://reactjs.org/docs/react-component.html#componentdidupdate – Nick Dec 28 '20 at 17:18
  • @tanjimanim updated my answer to _only_ fetch if `this.state.lat` is different than `prevState.lat` or `this.state.long` is different than `prevState.long` – Nick Dec 28 '20 at 17:22