Skip to content

Instantly share code, notes, and snippets.

@tylermercer
Last active April 2, 2021 16:43
Show Gist options
  • Save tylermercer/b1e7f8d80bdc89575fccb6e3cbc28fa0 to your computer and use it in GitHub Desktop.
Save tylermercer/b1e7f8d80bdc89575fccb6e3cbc28fa0 to your computer and use it in GitHub Desktop.
Asynchronously Providing a Value in Vue3
/* eslint vue/no-setup-props-destructure: 0 */
import { defineComponent, createVNode as h, ref, provide } from "vue"
const createInnerProvider = <T> () => {
return defineComponent({
props: {
toProvide: {
type: Object as () => T
},
provisionKey: {
type: Symbol,
required: true
}
},
provide() {
return {
[this.provisionKey]: this.toProvide
}
},
render() {
return this.$slots.default ? this.$slots.default() : null
}
})
}
const createAsyncProvider = <T> () => {
const inner = createInnerProvider<T>()
let provided: T | null = null;
return defineComponent({
props: {
toProvide: {
type: Object as () => Promise<T>,
required: true
},
provisionKey: {
type: Symbol,
required: true
}
},
components: {
inner
},
setup({ toProvide }) {
/*
* TODO: use `watch` rather than destructuring `props`, so that the eslint comment at the top
* can be removed. That would also allow this component to react when toProvide changes, and
* would probably fix the bug where injection fails after a hot reload.
*/
const loaded = ref(false)
toProvide.then((p: T) => {
loaded.value = true
provided = p
})
return {
loaded
}
},
render() {
if (this.loaded) {
return h(
inner,
{ toProvide: provided, provisionKey: this.provisionKey },
() => this.$slots.loaded ? this.$slots.loaded() : null
);
}
else {
return this.$slots.loading ? this.$slots.loading() : null
}
}
})
}
export default createAsyncProvider
<template>
<foo-provider :toProvide="foo" :provisionKey="fooKey">
<template #loading>
<p>Loading....</p> <!-- This will be rendered until foo resolves -->
</template>
<template #loaded>
<my-component/> <!-- MyComponent or any of its children may safely call inject(fooKey) -->
</template>
</template>
<script lang="ts">
import MyComponent from '@/components/MyComponent.vue'
import AsyncProvider from '@/components/AsyncProvider'
import {
Foo, //A class
createFooAsync, //A function that returns Promise<Foo>
fooKey //An InjectionKey<Foo>
} from '@/Foo'
export default defineComponent({
components: {
MyComponent,
FooProvider: createAsyncProvider<Foo>()
},
setup() {
const foo = createFooAsync()
return {
foo,
fooKey
}
}
})
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment