Using Suspense component in vue 3 along with Options API - vue.js

The docs https://vuejs.org/guide/built-ins/suspense.html#async-components says in order to use Suspense Components, we need to make the components "Asynchronous".
<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>
<template>
{{ posts }}
</template>
But I am not sure how to make components Async using Options API.

With the Options API, the component is made an async component by using an async setup():
export default {
👇
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}
That's actually documented just above the docs section the question links to.
demo

As the documentation states, only async setup function and asynchronous script setup are supported. Options API is legacy and isn't supposed to receive new features.
As it can be seen in source code, only setup is supported.
It may be possible to access internal component instance and follow the way setup works, e.g.:
beforeCreate() {
this.$.asyncDep = promise;
},
But this is undocumented and hacky way that can become broken without notice.

Related

BeforeRouteEnter not working in production with script setup

I used the beforeRouteEnter hook in vue-router to load data from two different endpoints using axios. I used promise.all() to load the data and then passed it to the component using next(). It seems to be working in development but when it is hosted on vercel the data isn't rendered on the component.
import axios from "axios"
import NProgress from "nprogress"
export default {
name: "DetailView",
beforeRouteEnter(to, from, next) {
NProgress.start()
const getDetails = axios.get(`api/grades/${ to.params.id }/`)
const getResults =
axios.get(`api/results/`, {
params: {
'grade-id': to.params.id,
}
})
Promise.all([getDetails, getResults])
.then(([details, results]) => {
next((vm) => {
vm.details = details.data
vm.results = results.data
})
})
.finally(NProgress.done())
},
}
I used a <script setup>...</script> for the setup function with the
import { ref } from "vue"
const details = ref({})
const grades = ref({})
I'm relatively new to javascript too and still trying to understand promises and async/await very well. Thank you
Finally found a solution to the problem. Components using <script setup> in vue are closed by default, he public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside <script setup>. From the vue docs.
I had to explicitly expose the properties used in the beforeRouteEnter navigation guard using the defineExpose compiler macro
defineExpose(
{
details,
results
}
)

Accessing Vue Nuxt plugins in layouts using fetch()

Wanted to ask if I am doing this right, it feels clunky. I am accessing a plugin in a Nuxt layout component. I want to dynamically generate content within the layout via the new fetch() api.
async fetch() {
this.notifications = await this.$root.context.app.contentfulClient.getNotifications()
this.treatments = await this.$root.context.app.contentfulClient.getTreatments()
},
It works as expected, but it seems a long handed way of accessing the plugin methods. Is this architecturally bad practice?
Nuxt plugins normally add properties to the Nuxt context, making them available from a component's this without having to explicitly drill into the context like you're doing.
Assuming your plugin injects contentfulClient like this:
// ~/plugins/contentfulClient.js
export default ({ app }, inject) => {
inject('contentfulClient', {
async getNotifications() {/*...*/},
async getTreatments() {/*...*/},
})
}
Then your component could use it like this:
async fetch() {
this.notifications = await this.$contentfulClient.getNotifications()
this.treatments = await this.$contentfulClient.getTreatments()
},

How to `emit` event out of `setup` method in vue3?

I know I can call the emit method from the setup method, but is there any way to emit event from any other functions without passing the emit method from setup method(not the the functions in the methods option, but a useXXX function) ?
setup function takes two arguments, First one is props.
And the second one is context which exposes three component properties, attrs, slots and emit.
You can access emit from context like:
export default {
setup(props, context) {
context.emit('event');
},
};
or
export default {
setup(props, { emit }) {
emit('event');
},
};
Source
in vue3 typescript setup
<script setup lang="ts">
const emit = defineEmits()
emit('type', 'data')
<script>
20220626
<script setup lang="ts">
const emit = defineEmits(['emit_a', 'emit_b'])
emit('emit_a')
emit('emit_b', 'emit_b_data')
<script>
With Vue 3 setup syntax sugar
<script setup lang="ts">
import { defineEmits } from 'vue'
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
function yourFunction (id: number) {
emit('change', id)
}
<script>
See docs: https://v3.vuejs.org/api/sfc-script-setup.html#typescript-only-features
Here's the proper way to emit events programmatically (using javascript) in vue3:
export default defineComponent({
// See: https://vuejs.org/guide/components/events.html#declaring-emitted-events=
emits: 'myEventName', // <--- don't forget to declare custom events emitted
setup(_, { emit }) {
emit('myEventName') // <--- emit custom event programmatically whenever we want
},
})
The emits function can just as easily be passed as a param to any function not declared inside setup.
Side-note regarding other answers: we should avoid using getCurrentInstance(), which was intended for library authors needing access to internals of vue components (a.k.a. this of vue v2), when there are better alternatives. Especially when those alternatives were designed explicitly for our use case.
methods: {
minhaFuncao(){
let data = "conteudo";
this.$emit("nomeDoMEuEvento", data);
}
}
SEE MORE AT :https://github.com/Carlos-Alexandre-Leutz/emitir-eventos-filho-pra-pai-com-dados-no-vue3
export const useEmit = () => {
const vm = getCurrentInstance()
const emitFactory = (event: string) => (...args: any[]) => vm.emit(event, ...args)
return {
emit: vm.emit,
emitModel: emitFactory('update:modelValue')
}
}
const useButtonHandlers = () => {
const { emit } = useEmit()
const onClick = () => emit('click')
return {
onClick
}
}
You can use getCurrentInstance from Vue. You can check it out in the docs.
Usage is like
function useFunctionThatEmitsSomething(){
const instance = getCurrentInstance();
// do something
instance.emit('event');
}
Edit: Even though this answer solves the author's problem, as per the linked docs, this method is intended only for ADVANCED use cases, e.g authoring a plugin or library. For common use cases, like building a simple SPA, using this is TOTALLY DISCOURAGED and should be avoided at all costs, since it can lead to unreadable and unmaintenable code. If you feel the need to use this in a case like that, you're probably doing something wrong.

Prefetching data with Nuxt to improve loading time

As far as I understand people are using server side rendering (ssr) to improve user experience and SEO with fast and content ready pages.
Recently I've started project with vue+nuxt in ssr mode.
What I've noticed is that when I try to prefetch data in nuxtServerInit action I have to wait for the async call to finish before page could be served to me.
As far as I know it will only hurt SEO and user experience.
export const actions = {
async nuxtServerInit ({ commit }) {
const response = await this.$axios.$get('games')
commit('setGameList', response.data)
}
}
Is there a way to actually prefetch data once and cache it for some period of time so that users would not be forced to wait?
Also what is the good usecase for nuxtServerInit? Cant understand the purpose of it..
Use The fetch Method
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>
<script>
export default {
async fetch ({ store, params }) {
let { data } = await axios.get('http://my-api/stars')
store.commit('setStars', data)
}
}
</script>
Remember to use Vuex to work well!
UPDATE: How to share the fetch function
1 - Create a file with the function:
// defaultFetch.js
module.exports = async function defaultFetch({ store, params }){
// Put some developer magic here to make the code works for you
let { data } = await axios.get('http://my-api/stars');
store.commit('setStars', data);
}
2 - Import and use in other components
// amazingComoponent1.vue
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>
<script>
import defaultFetch from "../utils/defaultFetch";
export default {
fetch: defaultFetch
}
</script>
// amazingComoponent2.vue
<template>
<h1>Stars: {{ $store.state.stars }}</h1>
</template>
<script>
import fetch from "../utils/defaultFetch";
export default {
fetch,
}
</script>
UPDATE 2: How to use and configure axios intance
Very easy, update the defaultFetch.js:
// defaultFetch.js
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
module.exports = async function defaultFetch({ store, params }){
// Put some developer magic here to make the code works for you
let { data } = await instance.get('http://my-api/stars');
store.commit('setStars', data);
}
Hope helps :)

Decentralizing functions in vuejs

Am from Angular2 whereby i was used to services and injection of services hence reusing functions how do i achieve the same in vuejs
eg:
I would like to create only one function to set and retrieve localstorage data.
so am doing it this way:
In my Login Component
this.$axios.post('login')
.then((res)=>{
localstorage.setItem('access-token', res.data.access_token);
})
Now in another component when sending a post request
export default{
methods:{
getvals(){
localstorage.getItem('access-token') //do stuff after retrieve
}
}
}
Thats just one example, Imagine what could happen when setting multiple localstorage items when retrieving one can type the wrong key.
How can i centralize functionality eg: setting token(in angular2 would be services)
There are a few different ways to share functionality between components in Vue, but I believe the most commonly used are either mixins or custom modules.
Mixins
Mixins are a way to define reusable functionality that can be injected into the component utilizing the mixin. Below is a simple example from the official Vue documentation:
// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
Custom module
If there are a lot of shared functionality with a logical grouping it might make sense to instead create a custom module, and import that where you need it (like how you inject a service in angular).
// localStorageHandler.js
const localStorageHandler = {
setToken (token) {
localStorage.setItem('access-token', token)
},
getToken () {
localstorage.getItem('access-token')
}
}
export default localStorageHandler
And then in your component:
// yourcomponent.vue
import localStorageHandler from 'localStorageHandler'
export default{
methods:{
getvals(){
const token = localStorageHandler.getToken()
}
}
}
Modules are using the more modern syntax of JavaScript, which is not supported in all browsers, hence require you to preprocess your code. If you are using the vue-cli webpack template it should work out of the box.