Dynamically data doesn't update when changing v-model input value in Vuejs
Asked Answered
R

3

5

I am building a weather app with this Weather API. I am trying to add an <input> field value that when it changes the city name, then updates the other values forecast.

I have created the <input> field which updates the city value & it should update the weather forecast accordingly. I know the v-model is working, however it doesn't change the data results. Only when I hard-code a different city in the Vue-instance the data updates the changes.

<template>
  <div class="home">
    <h1>{{ msg }}</h1>
    <p>A weather app built Vuejs & Open Weather App. Made by Manuel Abascal</p>
    <input type="text" v-model.lazy="currentWeather.name">
    <div class="forecast">
     <div v-if="this.currentWeather">
      <!-- Forecast stat values -->
      <h2>Right now:</h2>
      <div><strong>City:</strong> {{ currentCity }}</div>
      <div><strong>Longitude: </strong> {{ currentWeather.coord.lon }}</div>
      <div><strong>Latitude: </strong> {{ currentWeather.coord.lat }}</div>
      <div><strong>Weather condition </strong> {{ currentWeather.weather[0].description }}</div>
      <div><strong>Temperature Mid: </strong> {{  currentWeather.main.temp }} Farenheit</div>
      <div><strong>Temperature Max: </strong> {{  currentWeather.main.temp_max}} Farenheit</div>
      <div><strong>Temperature Min: </strong> {{  currentWeather.main.temp_min}} Farenheit</div>
      <div><strong>Humidity: </strong> {{  currentWeather.main.humidity }}%</div>
      <div><strong>Wind: </strong> {{  currentWeather.wind.speed }} mph</div>
     </div>
    </div>
  </div>
</template>

<script>
// import Axios
import axios from "axios"

export default {
  name: "Home",
  props: {
    msg: String,
  },
  data(){
    return {
      // current weather
      currentWeather: null,
      // current city
      currentCity: 'Montreal',
      // current country
      currentCountry: 'ca',
      unit: 'imperial'
    }
    this.$set(this.currentCity);
  },
  mounted(){
    // Make axios request to open weather api
    axios.get('https://api.openweathermap.org/data/2.5/weather?q='+this.currentCity+','+this.currentCountry+'&appid=fe435501a7f0d2f2172ccf5f139248f7&units='+this.unit+'')
    .then((response) => {
        // takes response object & stores it in currentWeather
        this.currentWeather = response.data

    })
    .catch(function (error) {
        // handle error
        console.log(error);
    })
  }
};
</script>

<style scoped lang="scss">

</style>

I am trying when I change to cities like Montreal, Toronto, Ottawa, Alberta, etc..it changes the forecast accordingly.

Rothberg answered 11/6, 2019 at 15:7 Comment(0)
P
2

You have no event handler for currentCity changes. So your code will work on initial load (i.e on mounted) and changes to currentCity will not change any weather data.

You need to add @change to the input and fetch new api data every time it changes.

below is the sample code

new Vue({
  el: '#app',
  data() {
    return {
      // current weather
      currentWeather: null,
      // current city
      currentCity: 'Montreal',
      // current country
      currentCountry: 'ca',
      unit: 'imperial'
    }
    this.$set(this.currentCity);
  },
  methods: {
    getWeather() {
      // Make axios request to open weather api
      fetch('https://api.openweathermap.org/data/2.5/weather?q=' + this.currentCity + ',' + this.currentCountry + '&appid=fe435501a7f0d2f2172ccf5f139248f7&units=' + this.unit + '')
        .then(res => res.json()).then(data => {
          // takes response object & stores it in currentWeather
          this.currentWeather = data;

        })
        .catch(function(error) {
          // handle error
          console.log(error);
        })
    }
  },
  mounted() {
    this.getWeather();
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
  <div class="home">
    <p>A weather app built Vuejs & Open Weather App. Made by Manuel Abascal</p>
    Search: <input type="text" v-model.lazy="currentCity" @change="getWeather">
    <div class="forecast" v-if="currentWeather && currentWeather.cod == 200">
      <!-- Forecast stat values -->
      <h2>Right now:</h2>
      <div><strong>City:</strong> {{ currentWeather.name }}</div>
      <div><strong>Longitude: </strong> {{ currentWeather.coord.lon }}</div>
      <div><strong>Latitude: </strong> {{ currentWeather.coord.lat }}</div>
      <div><strong>Weather condition </strong> {{ currentWeather.weather[0].description }}</div>
      <div><strong>Temperature Mid: </strong> {{ currentWeather.main.temp }} Farenheit</div>
      <div><strong>Temperature Max: </strong> {{ currentWeather.main.temp_max}} Farenheit</div>
      <div><strong>Temperature Min: </strong> {{ currentWeather.main.temp_min}} Farenheit</div>
      <div><strong>Humidity: </strong> {{ currentWeather.main.humidity }}%</div>
      <div><strong>Wind: </strong> {{ currentWeather.wind.speed }} mph</div>
    </div>
    <div v-else>
      "{{ currentCity }}" is not found
    </div>
  </div>
</div>
Peace answered 11/6, 2019 at 15:52 Comment(0)
J
2

There are 2 main issues that prevent your code from working as you expect.

v-model

The v-model in the input should be the currentCity data value, instead of the value in the response from the API, currentWeather.name.

This way, when the input changes, the currentCity will be updated, and you can react to it's change and request new data.

requesting data

Having the request for the weather done in the mounted hook is fine to get the data one time only, since this hook isn't executed again during the life of the component, a change in the city won't do anything.

solution

I would change the v-model to be currentCity, and add a watcher on currentCity so when it changes, it triggers a call to a function that gets the weather, also, making this watcher immediate, will guarantee it runs on component mount as well.

I have a jsfiddle here with the updated code.

Joerg answered 11/6, 2019 at 15:56 Comment(0)
A
2

You have two issues:

First, the input is bound the currentWeather.name instead of the currentCity

Second, you have the axios request in the mounted lifecycle. Even if the currentCity model change you are not defining what will happen when it changes. You need to add an api call when the currentCity change.

  1. Change the model of the input to the currentCity <input type="text" v-model="currentCity">

  2. Move the axios request to it's own method

    getWeather() {
        const url = 'https://api.openweathermap.org/data/2.5/weather?q=' + this.currentCity + ',' + this.currentCountry + '&appid=fe435501a7f0d2f2172ccf5f139248f7&units=' + this.unit + '';
        axios.get(url)
            .then((response) => {
                this.currentWeather = response.data;
            })
            .catch(function(error) {
                console.log(error);
            })
    }
    
  3. Bind the input change to the getWeather method

You can add the getWeather event to the input method of the currentCity input.

<input type="text" v-model="currentCity" @input="getWeather">

or add a watcher for the currentWeather

watch: {
    currentCity: function(newCity, oldCity) {
        this.getWeather();
    }
}

bonus

every time you write down or erase a letter of the input the method is going to fire. Add a debounce or timeout and it will fire after the milliseconds.

// import Axios
import axios from "axios"

export default {
    name: "Home",
    props: {
        msg: String,
    },
    data() {
        return {
            currentWeather: null,
            currentCity: 'Montreal',
            currentCountry: 'ca',
            unit: 'imperial'
        };
    },
    watch: {
        currentCity: function(newCity, oldCity) {
            this.debounceGetWeather();
        },
    },
    mounted() {
        this.getWeather();
    },
    methods: {
        debounceGetWeather() {
            setTimeout(() => {
                this.getWeather();
            }, 300);
        },
        getWeather() {
            axios.get('https://api.openweathermap.org/data/2.5/weather?q=' + this.currentCity + ',' + this.currentCountry + '&appid=fe435501a7f0d2f2172ccf5f139248f7&units=' + this.unit + '')
                .then((response) => {
                    this.currentWeather = response.data '
                })
                .catch(function(error) {
                    console.log(error);
                })
        },
    },
};
Abisha answered 11/6, 2019 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.