How Do I Share Async API Data across Components in Vue 3?

Viewed 1125

I have a top-level component that gets data from an API at regular intervals. I want to make a single API request and get all the data for my app in one place to reduce the number of requests to the API server. (FYI, my project looks like it's using Typescript but I'm not yet.)

Everything works fine in my top-level component:

//Parent

<script lang="ts">
import { defineComponent, ref, provide, inject, onMounted } from 'vue'
import getData from '@/data.ts'
  
export default defineComponent({
  setup(){
    const workspaces = ref([])
    
    onMounted(async () => {
      let api = inject('api') //global var from main.ts
      
      let data = await getData(api) //API request inside data.ts
      
      console.log(data.workspaces) //<-- data looks good here
      workspaces.value = data.workspaces
      
      //Trying to share workspaces with other components
      provide('workspaces', data.workspaces)
    })
    return {
      workspaces
    }
  }
})  
</script>

<template>
  {{ workspaces}} <!-- workspaces render fine here -->
</template>

But my child can't use the provide data via inject:

//Child

<script lang="ts">
import { defineComponent, inject, onMounted, ref } from 'vue'

export default defineComponent({
  setup(){
    let workspaces = ref([])
    
    onMounted(async () => {
      workspaces.value = await inject('workspaces') //<-- just a guess; doesn't work
    })
    
    return{
      workspaces
    }
  }
})
</script>

<template>
  {{ workspaces }} <!-- nothing here -->
</template>

I've made a couple assumptions as to the cause of the problem:

  1. The Child component loads before the parent's async stuff is done, and is therefore empty.
  2. I probably can't use project/inject in async scenarios like this.

So how can I share async data from an API across components in my app? Is my only option to go back to old-school props and pass the data down manually?

1 Answers

provide/inject are misused and subject to race conditions. Composition API is generally supposed to be at used on component initialization (setup, before any await) and not in onMounted. Even if there weren't such restriction, onMounted in parent component runs after the one in child component and can't provide a value at the time when a child is mounted.

The purpose of refs is to provide a reference to a value that can be changed later, so it could be passed by reference and stay reactive, this property isn't currently used.

It should be in parent component:

  setup(){
    const workspaces = ref([])
    provide('workspaces', workspaces)
    let api = inject('api')

    onMounted(async () => {
      let data = await getData(api)
      workspaces.value = data.workspaces
    })
    return { workspaces }

In child component:

  setup(){
    let workspaces = inject('workspaces')
    return { workspaces }
Related