Data() VS asyncData() in Nuxt & vue - vue.js

Both data() and async data() gives the same result (and it is obvious that the results from asyncData() override the results from data())
and both results in HTML code in the source code (i.e the code rendered in the server-side)
also, both can be used to "await" the data to be fetched (ex: using axios)
so, what is the difference between them?
<template>
<div>
<div>test: {{ test }}</div>
<div>test2: {{ test2 }}</div>
<div>test2: {{ test3 }}</div>
<div>test2: {{ test4 }}</div>
</div>
</template>
<script>
export default {
asyncData(app) {
return {
test: "asyncData",
test2: "asyncData2",
test3: "asyncData3"
};
},
data() {
return {
test: "data",
test2: "data2",
test4: "data4"
};
},
};
</script>
result:
test: asyncData
test2: asyncData2
test2: asyncData3
test2: data4

The simplest answer is data() is processed on the client side, however asyncData() section is processed on the server side on the call for Nuxt() once and on the client side once more.
The biggest advantage of nuxt is it's ability to render content on the server side. If you load your content using promise on the client side, say for example in the mounted section as:
data() {
return {
products: []
}
},
mounted() {
axios.get('/api/v1/products').then(response => {
this.products = response.data
})
}
the javascript code is sent to the client as it is and the browser is responsible to run the promise to fetch the data from the api. However if you put the promise inside asyncData:
asyncData() {
return axios.get('/api/v1/products').then(response => {
// Note that you can't access the `this` instance inside asyncData
// this.products = response.data
let products = response.data
return { products } // equivalent to { products: products }
})
}
The data fetching is done on the server side and the result is pre-rendered and an html with the data (rendered into it) is sent to the client. So in this case the client won't be receiving the javascript code to process the api call by itself, but instead it receives something like this:
<ul>
<li>
Product 1
</li>
<li>
Product 2
</li>
<li>
Product 3
</li>
</ul>
The result we return from asyncData is merged with what is in data. It's not replaced but merged.

You may want to fetch data and render it on the server-side. Nuxt.js adds an asyncData method that lets you handle async operations before setting the component data.
asyncData is called every time before loading the page component and is only available for such. It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes. This method receives the context object as the first argument, you can use it to fetch some data and return the component data.
The result from asyncData will be merged with data.
export default {
data () {
return { project: 'default' }
},
asyncData (context) {
return { project: 'nuxt' }
}
}

Nuxt's main attraction is the serverside rendering part, that helps with SEO. So we can assume any deviation from the normal "Vue-way" of doing things is most likely because it is in service of the SSR (which Vue naturally doesn't allow, hence we use Nuxt). Knowing that, we can pretty much say asyncData() contains the SEO-focused data that is send on the first page-load.
Short answer => use asyncData() for fetched template-based SEO-focused content.

Related

Nuxtjs Redis cache implementation for API calls inside components

I am using the plugin Nuxt Perfect Cache to server-side cache my QPI requests to an external service.
I am using the cacheFetch method on Component level and this component is loaded on a dynamic page (defined by its slug). When I navigate to the dynamic page, the API call is not cached in Redis, however when I reload the page, the caching happens as expected.
Below is how my code is structured:
_slug.js (for /users)
<template>
<h1>{{ user.name }}</h1>
<Posts :author = user.id>
</template>
<script>
import Posts from '~/components/Posts.vue'
export default {
components: { Posts },
async asyncData({params}) {
const user = await fetch(`/users/${params.slug}`)
.then(res => res.json())
}
}
</script>
And inside Posts.vue I use the perfect cache cacheFetch method to fetch the list of posts, something like:
props: ['author'],
async fetch() {
this.posts = await this.$cacheFetch({ key:`user--#{this.author}--posts`, expire: 60 * 60 },
async () => {
return await fetch(`/users/#{this.author}/posts`).then(res => res.json())
})
},
data() {
return {
posts: []
}
}
When I load the user page directly in the browser, the json response for the posts is saved in Redis as expected. When I navigate from within the application using a NuxtLink, the user page is displayed correctly (including the posts), but no key is set or get from Redis.
How can I ensure the API calls are cached when users interact with the app?
redis is only available in server side not client side when you are navigating in client side you don't have access to redis you can set absolute link to render server side when user is navigating but I don't recommend this.
the best solution is cache data in redis in your api.

VueJs Async Api Data best practice

I am building a headless SPA SSR in NuxtJs, and I am wondering what best practices is for making sure that the application only loads if connection has been established to the remote API.
This is currently my index.vue component:
<template>
<div class="wrapper">
<div class="content">
<div class="title">
<h1>{{site.meta.title}}</h1>
</div>
</div>
</div>
</template>
<script>
import Meta from '../classes/api/General/Meta'
export default {
data () {
return {
meta: null
}
},
created () {
Meta.getMeta().then((response) => {
this.meta = response.data
})
}
}
</script>
This sometimes resolves in that site.meta.title is undefined because the site is loading before the api data has been initialised. And yes, site.meta.title is defined under the hood in the api. So. Next step I was thinking was using async like following script:
<script>
import Meta from '../classes/api/General/Meta'
export default {
data () {
return {
meta: null
}
},
async created () {
await Meta.getMeta().then((response) => {
this.meta = response.data
console.log(response.data.site.logo)
})
}
}
</script>
Though this doesn't help anyway.
But with v-if="meta" it does help. Though: now it seems that Axios is not rendering the content in the code (ssr) anymore.
console.log is not something that you can really trust 100% of the time for async debugging tbh.
console.log(JSON.parse(JSON.stringify())) can help a bit more but it's still have some drawbacks sometimes.
As for best practices, both beforeCreate and created do run on both sides (server + client) so it's fine to use those. You can also use asyncData and the new fetch (it's a Nuxt hook, not the actual fetch API).
Beware of using the async/await syntax properly tho (no need for then here):
async created() {
const response = await Meta.getMeta()
this.meta = response.data
console.log(this.meta)
}
Also, with proper async/await, this one will never happen actually
because the site is loading before the api data has been initialised
You can read a bit more about the Nuxt lifecycle here: https://nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle
I'd recommend usually going the async fetch() way (non blocking, nice helpers), or async asyncData() if you need a blocking method. Middlewares can also be useful to make the whole process more flexible around your application.
You can get the whole explanation between the differences of fetch vs asyncData here: https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/
And if you want to have an example on how to use fetch in a real world scenario, you can read this one: https://nuxtjs.org/blog/build-dev-to-clone-with-nuxt-new-fetch/
So, It turns out that I got this the completely wrong way around.
In the newest nuxt versions, async fetch method is now included (build in).
With this, all rendering etc works fine and as expected.
My ended working code looks like this now:
<script>
export default {
async fetch () {
this.meta = await fetch(
'https://testurl.com/meta'
).then(res => res.json())
},
data () {
return {
meta: null
}
}
}
</script>
And the beauty with fetch, is that you can add listeners" like so:
<p v-if="$fetchState.pending">Fetching site</p>
<p v-else-if="$fetchState.error">Error happened</p>
<p>This content will be rendered server side even though waiting time</p>
I'm just posting this because my original question was a bit miss explained, and hope to help somebody else.
Edit:
I have marked kissu as answer (did see the post after i created this one), because it was explained so nice and well done!
Thanks :-)

How to watch on Route changes with Nuxt and asyncData

Hi everybody i'm trying to watch on route changes in my nuxt js app.
Here my middleware:
export default function ({ route }) {
return route; but i don't know what to write here
}
index.vue File
middleware: [routeReact]
i'm trying to write this:
app.context.route = route
but it says to me that app.context doesn't exist
Here's the point of my question i'm trying to update my data that gets from my api with axios on page if route changing
like this
this the page
i'm clicking link to next page :
but when i'm route to next page, nothing happens all data is the same:
here my asyncData code:
asyncData({ app }) {
return app.$axios.$get('apps/' + app.context.route.fullPath.replace(/\/categories\/?/, ''))
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
};
})
}
Thanks for your help
First thing, context.route or it's alias this.$route is immutable object and should not be assigned a value.
Instead, we should use this.$router and it's methods for programmatic navigation or <nuxt-link> and <router-link>.
As I understand, you need to render the same route, but trigger asyncData hook in order to update component's data. Only route query is changed.
Correct way to navigate to the same page but with different data is to use link of such format:
<nuxt-link :to="{ name: 'index', query: { start: 420 }}"
Then you can use nuxt provided option watchQuery on page component and access that query inside asyncData as follows:
watchQuery: true,
asyncData ({ query, app }) {
const { start } = query
const queryString = start ? `?start=${start}` : ''
return app.$axios.$get(`apps/${queryString}`)
.then(res => {
return {
info: res.results,
nextPage: res.next,
prevPage: res.prev
}
})
},
This option does not require usage of middleware. If you want to stick to using middleware functions, you can add a key to layout or page view that is used. Here is an example of adding a key to default layout:
<nuxt :key="$route.fullPath" />
This will force nuxt to re-render the page, thus calling middlewares and hooks. It is also useful for triggering transitions when switching dynamic routes of the same page component.

Vue axios (and fetch) responses not passing into data property

I have a Vue (2.2.1) component that should display a membership directory by making a request to a Laravel API. The request succeeds, and the response is correct when I console.log it. Likewise, the API request succeeds in Postman and the JSON object is correctly formatted. But, the response won't pass into the variable in the Vue instance data object to which I attempt to assign it. My code:
<template>
<div>
{{ entries }}
</div>
</template>
<script>
export default {
name: 'directory',
data() {
return {
entries: []
}
},
created() {
this.getEntries()
},
methods: {
getEntries: function() {
axios.get('/api/directory')
.then(response => {
this.entries = response.data;
});
}
}
}
</script>
When I load the page (after npm run dev), empty brackets representing the initial state of entries (an empty array) is displayed and not the raw JSON data. (I'm just testing now, not yet styling or building the table). However, if I add a console.log(this.entries) or console.log(response) or console.log(response.data), the correct form of the JSON is displayed in console and there are no errors.
I have replaced the axios code with the appropriate fetch code, including the ->json() line and get the same results. I have also used a "practice" API (JSONPlaceholder) to make sure it wasn't a problem with my API and likewise got the same results. This suggests that I'm doing something wrong in my Vue component <script>, but after hours of searching SO, Google, and Vue.js forum, I'm not finding anything. The closest matching link (Vue.js cannot set data returned from external axios response) was of no help, unfortunately.
I would make your entries a computed property so it will update on the call, it seems like your template is not updating a computed property may solve this.
Try this:
data(){
return {
dataEntries: null
}
computed: {
entries(){
if (dataEntries) return dataEntries
return []
}

Vue2 + Vuex - not rendering array that is set successfully in store

I am trying to render 209,579 options and I think that I am not using Vuex lifecycle + axios correctly
I see all options set in store state via Vuex in console.
No errors are displayed
Options are empty and not rendering the state cityNamesarray
main.js is exporting store to all components
I think I am not applying this lifecycle correctly, where I should use getters here, can someone bring order to my lifecycle?
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
isTrue: true,
cityNames: [],
},
getters:{
getCityNames(state){
return state.cityNames;
}
},
mutations: {
SetCityNames(state, cityNames){
state.cityNames = cityNames
},
actions: {
loadData({commit}){
axios.get('http://localhost:3000/allCities')
.then(function (response) {
commit('SetCityNames', response.data)
}).catch(function (error) {
console.log(error);
});
}
},
});
script
export default {
name: "Weather",
methods: {
allCityNames() {
this.$store.dispatch('loadData')
}
},
created() {
this.allCityNames();
}
}
template
<select>
<option disabled value="">Please select one</option>
<option v-for="cityName in $store.cityNames">{{cityName}}</option>
</select>
Thanks,
Bud
I have changed my code to be executed from compute, only to find out the error (at last!) which was: maximum stacksize exceeded, at this point I understood that Vue is not letting me render such a huge array (209,579 items) into view.
part I - Code Change:
I have created an isLoaded state that is set to true once axios commits it's response,
I'm still not sure if this is the best method due to the async nature of axios's call, it could have not finished with commit('SetCityNames', response.data); and right after invoking the commit, it would invoke the next : commit('changeLoadedState');
so i've added to state:isLoaded: false
added a getter: didItLoad(state){return state.isLoaded}
added a mutation: changeLoadedState(state){state.isLoaded = true}
added a commit (commit('changeLoadedState');) to my axios call in actions:
loadData({commit}) {
axios.get('http://localhost:3000/allCities')
.then(function (response) {
commit('SetCityNames', response.data);
commit('changeLoadedState');
}).catch(function (error) {
console.log(error);
});
}
In my component I am still dispatching the axios call in methods since it being called first, and added a computed method for the render side, as follows:
computed:{
isLoaded(){
return this.$store.getters.didItLoad;
},
renderCities(){
return this.$store.getters.getCityNames;
}
}
In My rendered template I first check with my select the loaded status and only then populate the options:
<select v-if="isLoaded">
<option disabled value="">Please select one</option>
<option v-for="cityName in renderCities">{{cityName}}</option>
</select>
Part II - Change Payload Size
So after setting my code straight, I went into my node express server and changed my route's loop to stop at 1000 items, and all worked great.
At this point I was curious what happens what happens if I start adding zeroes, so at 10K items, takes 1-2 sec to load options, opening the dropdown starts to show signs of latency due to stress, at 50K items it takes around 5 seconds to open the dropdown.
Bottom Line
The issue is not the size of the array, Vuex works amazing, getting a 209,579 item array in ~800ms which included backend parsing by Express.js (my whole stack is local so no network latency).
I will try to create an autocomplete that would start listing from 2nd or 3rd character.
Thanks to replying members.
You have a getters named getCityNames.It is $store.getters.getCityNames not $store.cityNames.
So change
<option v-for="cityName in $store.cityNames">{{cityName}}</option>
to
<option v-for="cityName in $store.getters.getCityNames">{{cityName}}</option>
it would be better to refactor to use a computed property rather than inlining in the template.
<option v-for="cityName in cityNames">{{cityName}}</option>
//script
computed: {
cityNames() {
return this.$store.getters.getCityNames;
}
}