clzola
clzola

Reputation: 2025

How to properly pass data to sibling component using VueJS?

Let's say I have CountrySelect and CitySelect, where in CountrySelect are listed all countries and in CitySelect are only listed those cities of currently selected country...

What would the proper way to pass newly selected country. As I have read in vuejs documentation:

In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.

So in this case, I would detect change of select box inside of CountrySelect and fire event changed and along with it a country object. Then the parent component of CountrySelect would listen for this event and then when that happens will update its attribute country. Attribute country is "observed" in parent component and changing its value will cause for DOM to update, so HTML attribute on <city-select> tag called country would change. But then how would I detect property change of CitySelect component, since I need to reload cities via AJAX. I think of putting country property in watch object in CitySelect but that does not seems like an elegant solution to me, it doesn't feels quire right to do...

<template>
    <parent>
        <country-select name="country_id" v-model="country"></country-select>
        <city-select name="city_id" :country="country" v-model="city"></city-select>
    </parent>
<template>

<script>
    export default {
        data: function() {
            return {
                country: null,
                city: null,
            };
        }
    }
</script>

Other way around that I think is if do something like this in parent:

this.$refs.citySelect.emit('countryChanged', this.country)

or:

this.$refs.citySelect.countryChanged(this.country)

I do not know if these two are possible... So what would be the correct way for CountrySelect component to tell to its sibling component CitySelect to update cities list...

Upvotes: 2

Views: 3572

Answers (1)

Bert
Bert

Reputation: 82449

By passing country as a property to your CitySelect component, you are already doing what you need to do. When country changes, the changed value will be passed to the CitySelect component. You simply need to make sure your CitySelect component is aware of the changes.

Here is a working example.

console.clear()

// Simulated service for retrieving cities by country
const CityService = {
  cities: [
    {id: 1, country: "USA", name: "New York"},
    {id: 2, country: "USA", name: "San Francisco"},
    {id: 3, country: "USA", name: "Boston"},
    {id: 4, country: "USA", name: "Chicago"},
    {id: 5, country: "England", name: "London"},
    {id: 6, country: "England", name: "Bristol"},
    {id: 7, country: "England", name: "Manchester"},
    {id: 8, country: "England", name: "Leeds"},
  ],
  getCities(country){
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(this.cities.filter(city => city.country === country)), 250)
    })
  }
}

Vue.component("CountrySelect",{
  props: ["value"],
  template: `
  <select v-model="selectedCountry">
    <option v-for="country in countries" :value="country">{{country}}</option>
  </select>
  `,
  data(){
    return {
      countries: ["USA", "England"]
    }
  },
  computed:{
    selectedCountry:{
      get(){return this.value},
      set(v){this.$emit('input', v)}
    }
  }
})

Vue.component("CitySelect", {
  props: ["value", "country"],
  template: `
  <select v-model="selectedCity">
    <option v-for="city in cities" :value="city">{{city.name}}</option>
  </select>
  `,
  data(){
    return {
      cities: []
    }
  },
  computed:{
    selectedCity:{
      get(){return this.value},
      set(v){this.$emit('input', v)}
    }
  },
  watch:{
    country(newCountry){
      // simulate an AJAX call to an external service
      CityService.getCities(newCountry).then(cities => this.cities = cities)
    }
  }
})

new Vue({
  el: "#app",
  data:{
    country: null,
    city: null
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <country-select v-model="country"></country-select>
  <city-select :country="country" v-model="city"></city-select>
  <hr>
  Selected Country: {{country}} <br>
  Selected City: {{city}}
</div>

Upvotes: 5

Related Questions