Vue/Nuxt: trying to understand SSR - vue.js

I've used Vue as a client-side library for years but I unfortunately never dove into server-side rendering, or understand it much, for that matter. I was watching a few tutorials on it where it gave an example of normal client-side fetching with the mount() hook.
<template>
<div v-for="item in items">
<div> {{ item.name }} </div>
</div>
</template>
data() {
return {
items: []
}
}
mount() {
fetch('http://www.myendpoint.com')
.then(response => {
this.items = response;
}
}
vs using Nuxt's asyncData option instead:
<template>
<div v-for="item in items">
<div> {{ item.name }} </div>
</div>
</template>
asyncData() {
fetch('http://www.myendpoint.com')
.then(response => {
this.items = response;
}
}
does this just mean that async data would run long before mountwould ever run since it's run before the component is even loaded so the data will be ready? What happens if that fetch call takes a while to load then...doesn't the same side effect happen? Rather than a component without data ready usingmount()` if the fetch takes long, you'd just have nothing at all loaded until the fetch completes if using SSR?

Nuxt is basically a Vue app but with some server logic done before serving the SPA.
Here are the 2 ways of using some data fetching hooks in Nuxt:
fetch() (it's actual hook's name, nothing related to the fetch API), I do recommend to use this one
asyncData (pretty much the same but less flexible although can block the page render until all the data is properly fetched)
More info can be found between both here: https://nuxtjs.org/docs/2.x/features/data-fetching
A whole question is available here too: Difference between Asyncdata vs Fetch
Lastly, you probably saw the lifecyle for Vue: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
Here is the one of Nuxt, which adds a bit more layers to Vue's SPA only ones: https://nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle

Related

When I click the nuxt js button the response appears

I want to hide the responses of a nuxt js project and post requests with SSR.
This is possible when the page first loads.
<template>
<p v-if="$fetchState.pending">Fetching mountains...</p>
<p v-else-if="$fetchState.error">An error occurred :(</p>
<div v-else>
<h1>Nuxt Mountains</h1>
<ul>
<li v-for="mountain of mountains">{{ mountain.title }}</li>
</ul>
<b-btn #click="$fetch">Refresh</b-btn>
</div>
</template>
<script>
export default {
data() {
return {
mountains: []
}
},
async fetch() {
this.mountains = await fetch(
'https://api.nuxtjs.dev/mountains'
).then(res => res.json())
}
}
</script>
However, when the button is clicked, apiler appears in the browser network response.
when the page loads
Clicking the button
The reason the request doesn’t appear in the network tab on page load is because nuxt runs the fetch method on the server, and injects the response in to the page component before it loads. You want a button on that same page to fetch the data again, but you don’t want the data request to appear in the network tab?
If so, I think you’re going to have to refresh the page. That way, the fetch method runs again before the page loads, and your user won’t see the request, just like in a traditional SSR app. However this seems like an unusual use case for a Nuxt app!
Instead of:
<b-btn #click="$fetch">Refresh</b-btn>
Use:
<b-btn #click="this.$router.go()”>Refresh</b-btn>

Vue- best practice for loops and event handlers

I am curious if it is better to include methods within loops instead of using v-if. Assume the following codes work (they are incomplete and do not)
EX: Method
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<span v-on:click="insertPrompt($event)">
{{ d }}
</span>
</div>
</div>
</template>
<script>
export default {
data() {
data:[
.....
]
},
methods:{
insertPrompt(e){
body.insertBefore(PROMPT)
}
}
}
</script>
The DOM would be updated via the insertPrompt() function which is just for display
EX: V-IF
//Parent
<template >
<div>
<div v-for="(d, i) in data" v-bind:key="i">
<child v-bind:data="d"/>
</div>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data() {
data:[
.....
]
},
}
</script>
//Child
<template>
<div>
<span v-on:click="display != display">
{{ d }}
</span>
<PROMPT v-if="display"/>
</div>
</template>
<script>
import child from './child'
export default {
components:{
child
},
data(){
return {
display:false
}
},
props: {
data:{
.....
}
},
}
</script>
The PROMPT is a basic template that is rendered with the data from the loop data click.
Both methods can accomplish the same end result. My initial thought is having additional conditions within a loop would negatively impact performance?!?!
Any guidance is greatly appreciated
Unless you are rendering really huge amounts of items in your loops (and most of the times you don't), you don't need to worry about performance at all. Any differences will be so small nobody will ever notice / benefit from having it a tiny touch faster.
The second point I want to make is that doing your own DOM manipulations is often not the best idea: Why do modern JavaScript Frameworks discourage direct interaction with the DOM
So I would in any case stick with the v-if for conditionally rendering things. If you want to care about performance / speed here, you might consider what exactly is the way your app will be used and decide between v-if and v-show. Citing the official documentation:
v-if is “real” conditional rendering because it ensures that event
listeners and child components inside the conditional block are
properly destroyed and re-created during toggles.
v-if is also lazy: if the condition is false on initial render, it
will not do anything - the conditional block won’t be rendered until
the condition becomes true for the first time.
In comparison, v-show is much simpler - the element is always rendered
regardless of initial condition, with CSS-based toggling.
Generally speaking, v-if has higher toggle costs while v-show has
higher initial render costs. So prefer v-show if you need to toggle
something very often, and prefer v-if if the condition is unlikely to
change at runtime.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
There are numerous solutions to solving this issue, but let's stick to 3. Options 2 and 3 are better practices, but option 1 works and Vue was designed for this approach even if hardcore developers might frown, but stick yoru comfort level.
Option 1: DOM Manipulation
Your data from a click, async, prop sets a condition for v-if or v-show and your component is shown. Note v-if removes the DOM element where v-show hides the visibility but the element is still in the flow. If you remove the element and add its a complete new init, which sometimes works in your favor when it come to reactivity, but in practice try not to manipulate the DOM as that will always be more expensive then loops, filters, maps, etc.
<template >
<div>
<div v-for="(d, i) in getData"
:key="i">
<div v-if="d.active">
<child-one></child-one>
</div>
<div v-else-if="d.active">
<child-two></child-two>
</div>
</div>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
data: [],
}
},
computed: {
getData() {
return this.data;
},
},
mounted() {
// assume thsi woudl come from async but for now ..
this.data = [
{
id: 1,
comp: 'ChildOne',
active: false
},
{
id: 2,
comp: 'ChildTwo',
active: true
},
];
}
}
</script>
Option 2: Vue's <component> component
Always best to use Vue built in component Vue’s element with the is special attribute: <component v-bind:is="currentTabComponent"></component>
In this example we pass a slug or some data attribute to activate the component. Note we have to load the components ahead of time with the components: {}, property for this to work i.e. it has to be ChildOne or ChildTwo as slug string. This is often used with tabs and views to manage and maintain states.
The advantage of this approach is if you have 3 form tabs and you enter data on one and jump to the next and then back the state / data is maintained, unlike v-if where everything will be rerendered / lost.
Vue
<template >
<div>
<component :is="comp"/>
</div>
</template>
<script>
import ChildOne from "./ChildOne";
import ChildTwo from "./ChildTwo";
export default {
components: {
ChildOne,
ChildTwo
},
props: ['slug'],
data() {
return {
comp: 'ChildOne',
}
},
methods: {
setComponent () {
// assume prop slug passed from component or router is one of the components e.g. 'ChildOne'
this.comp = this.slug;
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
Option 3: Vue & Webpack Async and Dynamic components.
When it comes to larger applications or if you use Vuex and Vue Route where you have dynamic and large number of components then there are a number of approaches, but I'll stick to one. Similar to option 2, we are using the component element, but we are using WebPack to find all Vue files recursively with the keyword 'module'. We then load these dynamically / asynchronous --- meaning they will only be loaded when needed and you can see this in action in network console of browser. This means I can build components dynamically (factory pattern) and render them as needed. Example, of this might be if a user adds projects and you have to build and config views dynamically for projects created e.g. using vue router you passed it a ID for a new project, then you would need to dynamically load an existing component or build and load a factory built one.
Note: I'll use v-if on a component element if I have many components and I'm unsure the user will need them. I don't want to maintain state on large collections of components because I will end up memory and with loads of observers / watches / animations will most likely end up with CPU issues
<template >
<div>
<component :is="module" v-if="module"/>
</div>
</template>
<script>
const requireContext = require.context('./', true, /\.module\.vue$/);
const modules = requireContext.keys()
.map(file =>
[file.replace(/(.*\/(.+?)\/)|(\.module.vue$)/g, ''), requireContext(file)]
)
.reduce((components, [name, component]) => {
// console.error("components", components)
components[name] = component.default || component
return components
}, {});
export default {
data() {
return {
module: [],
}
},
props: {
slug: {
type: String,
required: true
}
},
computed: {
getData() {
return this.data;
},
},
methods: {
setModule () {
let module = this.slug;
if (!module || !modules[module]) {
module = this.defaultLayout
}
this.module = modules[module]
}
},
mounted() {
this.nextTick(this.setModule())
}
}
</script>
My initial thought is having additional conditions within a loop would negatively impact performance?
I think you might be confused by this rule in the style guide that says:
Never use v-if on the same element as v-for.
It's only a style issue if you use v-if and v-for on the same element. For example:
<div v-for="user in users" v-if="user.isActive">
But it's not a problem if you use v-if in a "child" element of a v-for. For example:
<div v-for="user in users">
<div v-if="user.isActive">
Using v-if wouldn't have a more negative performance impact than a method. And I'm assuming you would have to do some conditional checks inside your method as well. Remember that even calling a method has some (very small) performance impact.
Once you use Vue, I think it's a good idea not to mix it up with JavaScript DOM methods (like insertBefore). Vue maintains a virtual DOM which helps it to figure out how best to update the DOM when your component data changes. By using JavaScript DOM methods, you won't be taking advantage of Vue's virtual DOM anymore.
By sticking to Vue syntax you also make your code more understandable and probably more maintainable other developers who might read or contribute to your code later on.

Wrong component structure - prop-problems

I'm a noob and I'm trying to get my head around vue.js. In a particular route I have a view that looks like this
<template>
<div class="section">
<h1 class="title">Maintenance</h1>
<app-tabs></app-tabs>
<div class="columns">
<div class="column is-one-third">
<app-taglist></app-taglist>
</div>
<div class="column is-two-thirds">
<app-form></app-form>
</div>
</div>
</div>
</template>
<script>
import Taglist from "#/components/Taglist.vue";
import Tabs from "#/components/Tabs.vue";
import Form from "#/components/Form.vue";
export default {
}
</script>
Now I do understand the basics of passing data from a component to a child component, but what I'm actually trying to do is to pass data from app-tabs to app-taglist and from app-taglist to app-form. I'm starting to think that I'm attacking this all wrong, but if I do change my structure so that app-taglist is a child of app-tabs and app-form is a child of app-taglist - I can't really make proper use of the simple bulma, responsive, styling ...?
BTW: all components is registered globally at this time.
What kind of aproach would you advise me to look into - keeping in mind that I'm a noob.
Once you start getting to the point where you need to handle passing data between multiple components then I would consider using Vuex in order have a global component state that is accessible from all of your components.
Basically you can then create a totally separate "store" to hold your app's state (data):
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
And then from within each component, you can:
Use "getters" to read data from the store
Use "actions" to dispatch async functions that will "mutate" the store
e.g:
store.commit('increment') // this is a mutation
console.log(store.state.count) // -> 1

Method in vue js runs infinitely

I have a method that is called in vue js template. However, when I run the web site, this method is called infinitely
<template>
<div class="strip_list wow fadeIn" v-for="(row, index) in model.data">
<i class="icon_star voted" v-for="(val, index) in getOverallRating(row.id)">
</i>
</div>
</template>
<script>
methods: {
getOverallRating(id)
{
axios.get(`${this.apiReview}?id=${id}`).then(response =>
{
this.overall = response.data
})
return this.overall
}
}
</script>
I expect to give an id of the user, then the method should get an ID send it to the laravel controller, get calculate the rating according the entries in DB and return the result.
So, what you want to do is remove anything that would generate an api call out of your templates loop. What happens is, that every time the data changes, you re-render the template, and since you have an api call in your template, every time you render you request new data, that's why you're getting an infinite loop.
You should store the data you get from the api in a variable, and initiate API calls from outside of the loop.
<template>
<div class="strip_list wow fadeIn" v-for="(row, index) in model.data">
<i class="icon_star voted" v-for="(val, index) in overall(row.id)">
{{val}}
</i>
</div>
</template>
data: () => {
overall: {}
},
methods: {
getAll() {
// loop through all rows and make api request
this.model.data.forEach(r => this.getOverallRating(r.id))
}
getOverallRating(id) {
// make api request
axios
.get(`${this.apiReview}?id=${id}`)
.then(response => {
// and save into overall, where the id is the key
this.overall[id] = response.data
})
},
},
created(){
// initialize loading during create
this.getAll();
}
This can be further improved by not rendering anything 'till all rows are fetched. You could do that by defining another variable in data, that gets populated during getAll, and updated every time the api gets a response. But Ideally you'd be able to call the API for all reviews at once, not one at a time.

Getting ref of component in async v-for

I have a list of items that don't get created until after an async call happens. I need to be able to get the getBoundingClientRect() of the first (or any) of the created items.
Take this code for instance:
<template>
<div v-if="loaded">
<div ref="myItems">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
<div v-else>
Loading...
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items: []
}
},
created() {
axios.get('/url/with/some/data.json').then((response) => {
this.items = response.data;
this.loaded = true;
}, (error) => {
console.log('unable to load items');
});
},
mounted() {
// $refs is empty here
console.log(this.$refs);
// this.$refs.myItems is undefined
}
};
</script>
So, I'm trying to access the myItems ref in the mounted() method, but the this.$refs is empty {} at this point. So, therefore, I tried using a component watch, and various other methods to determine when I can read the ref value, but have been unsuccessful.
Anyone able to lead me in the right direction?
As always, thanks again!!
UPDATES
Added a this.$watch in the mounted() method and the $refs still come back as {}. I then added the updated() method to the code, then was able to access $refs there and it seemed to work. But, I don't know if this is the correct solution?
How does vuejs normally handle something like dynamically moving a div to an on-screen position based on async data? This is similar to what I'm trying to do, grab an element on screen once it has been rendered first (if it even should be rendered at all based on the async data), then access it to do something with it (move it to a position)?
Instead of doing on this.$refs.myItems during mounted, you can do it after the axios promise returns the the response.
you also update items and loaded, sou if you want to use watch, you can use those
A little late, maybe it helps someone.
The problem is, you're using v-if, which means the element with ref="myItems" doesn't exist yet. In your code this only happens when Axios resolves i.e. this.loaded.
A better approach would be to use a v-show.
<template>
<div>
<div v-show="loaded">
<div ref="myItems">
<div v-if="loaded">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-show="!loaded">
Loading...
</div>
</div>
</template>
The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
https://v2.vuejs.org/v2/guide/conditional.html#v-show