Static HTML Elements loads before data fetch in Vue Application - javascript

I'm using VueJS and VueRouter to create my application. My problem is that when I'm fetching data the static HTML elements used on the Vue Component load before the actual fetch, causing empty forms and tables to be displayed before data is fetched. I call my fetch method from the created() lifecycle hook, so the result is confusing me. Here are my code so far:
<template>
<div id="app">
<h1>My Data length: {{fetchedData.length}}</h1>
<li v-for="value in fetchedData">{{value}}</li>
</div>
</template>
export default {
name: "MyVueComponent",
data() {
return {
fetchedData: [],
}
},
methods: {
async getData() {
await fetch('https://cli-vue-application.herokuapp.com/user/getUser', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
.then(function (response) {
return response.json();
}).then(function (data) {
this.fetchedData= data
}.bind(this));
}
},
created() {
this.getData();
}
}
The data is fetched correctly, but the h1 element displays first and the length is "0" for the first couple of seconds, because the fetch happnds after the static HTML is loaded as explained above.

you could wrap everything in a simple if fetchedData has something see everything, otherwise not:
<template>
<div id="app">
<div v-if="fetchedData.length">
<h1>My Data length: {{fetchedData.length}}</h1>
<li v-for="value in fetchedData">{{value}}</li>
</div>
</div>
</template>
if you want you can also add an else, that is, if nothing has arrived yet, instead of showing nothing enter a "loading" text or better a spinner like these:
https://bootstrap-vue.org/docs/components/spinner#spinners
therefore:
<template>
<div id="app">
<div v-if="fetchedData.length">
<h1>My Data length: {{fetchedData.length}}</h1>
<li v-for="value in fetchedData">{{value}}</li>
</div>
<div v-else>
Loading...
//Or your spinner
</div>
</div>
</template>

Related

When moving between Dynamic Pages with NuxtLink, the data of store cannot be retrieved

A demo is provided below.
stackblitz
When I move from the top page to a post page, the correct content is displayed, but when I move from a post page to another post page, the correct content is not displayed.
However, reloading will display the correct content.
Could you please show us how to display the correct content after transitioning from one posting page to another?
The code for the submission page is as follows.
// pages/post/_id.vue
<template>
<div></div>
</template>
<script>
import { fetchPosts } from '../../lib/post';
export default {
name: 'Post',
layout: 'post/index',
async asyncData({ route, store }) {
const posts = await fetchPosts();
const post = posts.find(({ id }) => id === route.params.id);
store.dispatch('setPost', post);
store.dispatch('setPosts', posts);
},
};
</script>
// layouts/post/index.vue
<template>
<div>
<h1 v-if="post">{{ post.title }}</h1>
<p v-if="post">{{ post.title }} page</p>
<ul>
<li v-for="post in posts" :key="post.id">
<NuxtLink :to="'/post/' + post.id">
{{ post.title }}
</NuxtLink>
</li>
</ul>
<NuxtLink to="/">Top</NuxtLink>
</div>
</template>
<script>
export default {
data() {
return {
post: null,
posts: [],
};
},
created() {
this.post = this.$store.getters['post'].post;
this.posts = this.$store.getters['posts'].posts;
},
};
</script>
The process flow is as follows
pages retrieves data from the server and dispatches it to the store
laytous retrieves data from the store and displays the data in laytous
I know that the use of pages and layouts is not common, but the project I am currently working on specifies this usage and I cannot change this usage.
That's because the layout was already rendered when the route changes (when the route changes, the layout/component used is already created, so the hook created is not called again), a simple fix would be to add a watch for the route:
...
watch: {
'$route': function (value) {
this.post = this.$store.getters['post'].post;
this.posts = this.$store.getters['posts'].posts;
}
},
...

Vue not rendering fetched html

I am using vue in shopify and am working on a collection page. When I click on a filter, it‘s an href and it updates the url and reloads the page.
So I have a product grid
<div class="grid-wrapper">
<template v-for="(product, index) in collection.products">
<div class="product-item"></div>
</template>
</div>
And my idea was to just use the same url with fetch so the page doesn‘t reload.
I did this
fetch('/collections?variant=black')
.then(res => res.text())
.then(html => new DOMParser().parseFromText(html, 'text, html'))
.then(data => {
document.querySelector('.grid-wrapper').innerHTML = data.querySelector('.grid-wrapper').innerHTML
})
This does not work because I get back the actual <template v-for…> as the new innerHTML and vue isnt taking over. How can I solve this
In shopify I converted the object like so
const collection = (() => {
const collection = {{ collection | json }}
const products = {{ collection.products | json }}
collection.products = products
return collection
})();
Then in my vue instance
new Vue.createApp({
data() {
collection: collection
}
}).mount('#app')
You're approaching this in the traditional JavaScript way of manipulating the DOM directly. In Vue, we set state which can then be rendered by your template.
Instead:
Create a data attribute to store your state
Under methods, write a function to fetch your data, then update the components data
Call your function in the components created hook
In you're template render the results
Don't forget to check it's present first, with v-if
You can use v-for to iterate over, and render lists
Here's a working demo
I don't have access to your API endpoint, so for demo purposes, am just using the GitHub API, to fetch and render a list of all repos in the Vue.js organization.
Here's what it looks like:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
name: 'dbzx10299-demo',
data() {
return {
loaded: false,
response: null,
}
},
methods: {
fetchData() {
const demoEndpoint = 'https://api.github.com/orgs/vuejs/repos';
fetch(demoEndpoint)
.then(response => response.json())
.then(data => {
this.response = data;
this.loaded = true;
})
},
},
mounted() {
this.fetchData();
},
})
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<div id="app">
<div class="hello">
<h2>Vue Repo List - Data fetching example</h2>
<div v-if="!loaded">Loading...</div>
<ul v-else>
<li v-for="(repo, index) in response" :key="index">
<a :href="repo.html_url" :title="repo.description" target="_blank">
{{ repo.name }}
</a>
<i>★ {{ repo.stargazers_count }}</i>
</li>
</ul>
</div>
</div>

vue.js post list not updating after form submission

In my vue app I have two components one which is a form that posts the form data to my api. And the other gets and displays these posts in a section on the page. My issue is that when I submit a new post the posts lists aren't updated. The data stays the same unless I refresh the page. How can I get my posts list to update when I submit the form?
My Code:
client/src/App.vue
<template>
<div id="app">
<MainHeader :modalVisability="modal" v-on:showModal="toggleModal" />
<div id="content_wrap">
<Summary />
</div>
<OppForm :modalVisability="modal" />
</div>
</template>
<script>
import MainHeader from './components/MainHeader.vue';
import OppForm from './components/oppForm.vue';
import Summary from './components/Summary.vue';
export default {
name: 'App',
components: {
MainHeader,
Summary,
OppForm
},
data () {
return {
modal: false
}
},
methods: {
toggleModal (modalBool) {
this.modal = modalBool;
}
}
}
</script>
client/src/components/oppForm.vue
<template>
<div id="opp_form_modal" >
<form #submit.prevent="SubmitOpp" v-if="modalVisability">
<input type="text" name="company_name" v-model="company_name">
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'oppForm',
props: {
modalVisability: Boolean,
},
data () {
return {
company_name: ''
}
},
methods: {
SubmitOpp () {
axios.post('http://localhost:5000/', {
company_name: this.company_name,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
}
</script>
client/src/components/Summary.vue
<template>
<div id="summary_section">
<h2>Summary</h2>
<div id="summary_board">
<div class="column">
<div class="head">
<h3>Opportunities</h3>
</div>
<div class="body">
<div class="post"
v-for="(post, index) in posts"
:key="index"
>
<p class="company">{{ post.company_name }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return{
posts: []
};
},
created() {
axios.get('http://localhost:5000/')
.then(res => {
// console.log(res);
const data = res.data;
this.posts = data;
})
.catch(error => console.log(error));
}
}
</script>
The problem is that you're actually fetching your posts only on the app creation (i.e. inside the created() method).
You should wrap your axios call inside a function updatePosts() and then call it whenever you add a new post successfully, or you could create a custom event that is triggered whenever a new post is added.
created() is called only once (see vue lifecycle) so you fetch API before submitting form.
Try to add some console.log to understand what is called when.
You could use an global event bus and send form value as event data to summary. I could imagine also a solution where event is used to "tell" summary that form was submitted (just boolean, not data itself). In summary you then call API each time you receive event.
Or simple add an "update" button to summary to manually call API.
See Communication between sibling components in VueJs 2.0
or global vue instance for events for detailed examples.

VueJS - V-for doesn't re-render after data is updated and needs page refresh to see the change

So this code does adds or delete an entry, But whenever I add or delete, it does not show the changes or rather re-render. I need to refresh the page in order to see what changes had.
note: I am using ME(Vue)N stack.
I have this code:
<script>
import postService from '../../postService';
export default {
name: 'postComponent',
data() {
return {
posts: [],
error: '',
text: ''
}
},
async created() {
try {
this.posts = await postService.getPosts();
}catch(e) {
this.error = e.message;
}
},
methods: {
async createPost() {
await postService.insertPost(this.text)
this.post = await postService.getPosts();
// alert(this.post,"---")
},
async deletePost(id) {
await postService.deletePost(id)
this.post = await postService.getPosts();
// alert(this.post)
}
}
}
</script>
<template>
<div class="container">
<h1>Latest Posts</h1>
<div class="create-post">
<label for="create-post">input...</label>
<input type="text" id="create-post" v-model="text" placeholder="Create a post">
<button v-on:click="createPost">Post</button>
</div>
<!-- CREATE POST HERE -->
<hr>
<p class="error" v-if="error">{{error}}</p>
<div class="posts-container">
<div class="post"
v-for="(post) in posts"
v-bind:key="post._id"
v-on:dblclick="deletePost(post._id)"
>
{{ `${post.createdAt.getDate()}/${post.createdAt.getMonth()}/${post.createdAt.getFullYear()}`}}
<p class="text">{{ post.username }}</p>
</div>
</div>
</div>
</template>
sorry if there's an error in the snippet. I just needed to show the code and I cant make the script work on the code sample {}.
Any help would be appreciate. Vuejs beginner here.
This code is copied and typed through a youtube tutorial.
Your component has a data property posts, but you're assigning to this.post in several places in the code.
I suspect a typo, but it's also worth remembering that if this additional property (this.post) isn't available when the component is instantiated, it won't be (magically) converted into a reactive property when you create/assign to it.

VueJS: render new component after click

I'd like to render new component in vue.js as if it's new page.
I'm trying to do it with something called "dynamic component"
parent: Post.vue
child: Detail.vue
so, if one of the posts is clicked, Post is off and Detail is on.
The thing is I have to send clicked post's id to the Detail.
Here's some of my code.
Post.vue
<template>
<div>
<div v-if="loading">
loading...
</div>
<div v-else class="container">
<ul>
<li v-for="(post, index) in filteredPosts" v-bind:key="post.no">
<section class="post__main">
<div #click..?? class="main__title">{{post.title}}</div>
</section>
</li>
</ul>
</div>
</div>
</template>
<script>
created() {
axios.get(this.url, {
params: {
page: this.page,
ord: this.ord,
category: []
}
}).then(res => {
this.posts = res.data.list;
this.loading = false;
this.page++;
});
Detail.vue
<template>
<div>
{{contents}}
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Datail',
data() {
return {
req_no: null,
contents: {}
}
},
created() {
axios.get(url, {
params: {
req_no: this.req_no
}
}).then(res => {
this.contents = this.res.data
});
}
}
</script>
I feel like I can do it with props and v-if.
Can someone help me? Thanks!
Once a post is clicked, pass post id to the click handler. In the click handler, route to detail.vue passing post id as route param.
like below:
<li v-for="(post, index) in filteredPosts" v-bind:key="post.no">
<section class="post__main">
<div #click="onPostClick(post.id)" class="main__title">{{post.title}}</div>
</section>
</li>
And in your click handler:
onPostClick(id: number) {
this.$router.push({
name: 'details',
params: {
id
}
});
}
This will work provided you set up vue router correctly in your app and have a valid route for details.
You can access the post id in details component as follows:
created() {
this.postId = this.$route.params.id;
}
I would take a look at the <component> which takes a prop :to and renders a component, this is good for something like tabs where you are rendering different component from a the same general location on the page without reloading the whole page. See here:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
This seems to be a very good use case for you, just pass into the component the props you need.

Categories