Search code examples
vue.jsvue-component

Issues with Vue.js Options API when injecting fetched data to other components


I am using Vue.js Options API. The problem is that when I try to provide data for other components after fetching from API, injecting doesn't work and i have no idea why. Maybe provide is executing too fast and therefore passing an empty array? I don't know, please help.

App.vue file:

<script>
// import { RouterLink, RouterView } from 'vue-router'

import TheHeader from './components/TheHeader.vue'
import Filters from './components/filters/Filters.vue'
import Countries from './components/Countries.vue'

export default {
  data() {
    return {
      allCountries: []
    }
  },
  components: {
    TheHeader,
    Filters,
    Countries
  },
  methods: {
    fetchCountries() {
      fetch('countries.json')
        .then((res) => {
          if (res.ok) {
            return res.json()
          } else {
            return Promise.reject(`Http error: ${res.status}`)
          }
        })
        .then((res) => {
          this.allCountries = res
        })
        .catch((error) => {
          console.error(error)
        })
    }
  },
  created() {
    this.fetchCountries()
  },
  provide() {
    return {
      allCountries: this.allCountries
    }
  }
}
</script>

<template>
  <TheHeader />
  <main class="px-8 pb-12 sm:px-16">
    <Filters />
    <Countries />
  </main>
</template>

<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;600;800&display=swap');
html {
  font-family: 'Nunito Sans', sans-serif;
}
</style>

Countries.vue file:

<template>
  <div id="country-wrapper" class="grid grid-cols-auto-fill justify-center gap-12 md:gap-20">
    <CountryItem v-for="country in filteredCountries" :key="country.cca3" :country="country" />
  </div>
</template>

<script>
import CountryItem from './CountryItem.vue'

export default {
  inject: ['allCountries'],
  data() {
    return {
      filteredCountries: this.allCountries
    }
  },
  components: {
    CountryItem
  }
}
</script>

I tried with computed values, and rearranging the order in different ways, but it doesn't work


Solution

  • There's no way how allCountries can be reactive this way, it's used by value in data, before it's assigned to res.

    This is what ref pattern is for, it allows to use a reference instead of a value. It's unnecessary to use Vue ref composition API for this purpose, a way it's supposed to work in any Vue version is to pass an object everywhere.

    In a parent:

      data() {
        return {
          allCountries: { value: [] }
        }
      },
      provide() {
        return {
          allCountries: this.allCountries
        }
      },
      methods: {
        fetchCountries() {
          ...
          this.allCountries.value = res
        }
    

    In a child:

      inject: ['allCountries'],
      computed: {
        filteredCountries() { return this.allCountries.value }
      }
    

    Unless there may be different provided values with the same name across the application, this is the case for a global store.