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>
Related
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;
}
},
...
So I'm trying to create a dynamic tab menu with Vue 3 and slots. I got the tabs working, I have BaseTabsWrapper and BaseTab components. I need to be able to v-for with BaseTab component inside of a BaseTabsWrapper Component. Like this:
<section
id="content"
class="w-full mx-2 pr-2"
v-if="incomingChatSessions && incomingChatSessions.length"
>
<BaseTabsWrapper>
<BaseTab
v-for="chatSession in incomingChatSessions"
:key="chatSession.id"
:title="chatSession.endUser.name"
>
<p>{{ chatSession }}</p>
</BaseTab>
</BaseTabsWrapper>
</section>
An important caveat from the answers that I have found is that the incomingChatSessions object is asynchronous and coming from a websocket (I have tested that this object is working fine and bringing all the data correctly aka is never an empty object).
Inside of BaseTabsWrapper template. Important parts:
<template>
<div>
<ul
class="tag-menu flex space-x-2"
:class="defaultTagMenu ? 'default' : 'historic'"
role="tablist"
aria-label="Tabs Menu"
v-if="tabTitles && tabTitles.length"
>
<li
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
:title="title"
role="presentation"
:class="{ selected: title === selectedTitle }"
>
<a href="#" role="tab">
{{ title }}
</a>
</li>
</ul>
<slot />
</div>
</template>
And the script:
<script>
import { ref, useSlots, provide } from 'vue'
export default {
props: {
defaultTagMenu: {
type: Boolean,
default: true,
},
},
setup(props) {
const slots = useSlots()
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
const selectedTitle = ref(tabTitles.value[0])
provide('selectedTitle', selectedTitle)
provide('tabTitles', tabTitles)
return {
tabTitles,
selectedTitle,
}
},
}
</script>
This is the Tab component template:
<template>
<div v-show="title === selectedTitle" class="mt-4">
<slot />
</div>
</template>
<script>
import { inject } from 'vue'
export default {
props: {
title: {
type: String,
default: 'Tab Title',
},
},
setup() {
const selectedTitle = inject('selectedTitle')
return {
selectedTitle,
}
},
}
</script>
The important part in my script and the one that is giving me a lot of trouble is this one:
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
What I'm doing here is creating an array of tab titles based on the property "title" of each slot but when I load the page this array always have just one title, even if I'm fetching more title elements from the API. One thing that I have noticed is that if I force a re-render of the page from my code then the tabTitles array have the correct amount of elements and I got all the correct amount of tabs on my menu. I have tested that everything is working fine with the way I control asynchronicity with the data coming from the websocket in order to hidrate the "incomingChatSessions" array but as much as I try tabTiles always gets just one element no matter what.
i would do something like that :
computed(
() => slots.default()[0].children.map((tab) => tab.props.title)
)
it should update the computed property when the component is updated (like slot changes)
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>
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.
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.