I'm creating very simple app and I have a problem with getting info from furnitures.js:
export default [
{ id: 1, name: 'Kanapa Sydney', dim1: '2,25m', dim2: '1,45m x 1,95m'},
{ id: 2, name: 'Kanapa Alex', dim1: '1,95m', dim2: '1,45m x 1,95m'}
]
File ProductDetail contain app-prodrend component. The only thing I know is Id (from route params) and I want to display (id, name, dim1 and dim2) in this component (app-prodend).
ProductDetail.vue
<template>
<div class="prod-det">
<app-header style="background-color: black"></app-header>
<app-prodrend style="position: absolute; margin-top: 50vh" :prod="prods"></app-prodrend>
</div>
</template>
<script>
import header from '../Header';
import prodrend from './ProdDetRen';
export default {
data() {
return {
id: this.$route.params.id
}
},
components: {
appHeader: header,
appProdrend: prodrend
},
computed: {
prods(id) {
return 'kook'
}
}
}
</script>
ProdDetRen.vue
<template>
<h1>dawdwa {{ prod.id }}</h1>
</template>
<script>
export default {
props: ['prod']
}
</script>
I tried to make a getter in furn.js file:
furnDetail(state, index) {
const record = state.products.find(element => element.id == index);
return {
id: index,
name: record.name,
dim1: record.dim1,
dim2: record.dim2
}
}
I have no idea what to do. Thanks in advance
The solution i would recommend is using Vuex to manage your data. Vuex will give you the ability to create getters that you can import into the components that require data. With that data you can filter the data down to only the item you are looking for.
Ive created a repo on my Git that you can have a look at to get a better idea of what i mean.
Visit https://github.com/FloydWatson/furnitureapp
Ive utilized Vuex for state management and vue-router to create the routing paths that have an id in them.
Within the page furniture.vue I get the list of furniture and find the item im looking for using route parameters
computed: {
...mapGetters(["allFurniture"]),
// get the details here
loadedItem() {
return this.allFurniture.find((item) => item.id == this.$route.params.id);
},
},
This is done in a computed field so that if the data was updated our Vuex would let the page know and our data would dynamically update.
Ive tried to leave some comments in there so you can see whats going on easier. Feel free to ask me if theres any other clarification you need.
Happy coding
Related
I'm having small issue with provide/inject in my project.
In App.vue, I'm pulling data from DB and pushing it into object. With console log I checked and all data it's there.
<template>
<router-view />
</template>
<script>
export default {
provide() {
return {
user: this.user,
};
},
data() {
return {
user: '',
};
},
methods: {
///pulling data from DB
func() {
fetch("url")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => {
const user = [];
for (const id in data) {
user.push({
id: data[id].user_id,
firstName: data[id].user_firstname,
lastName: data[id].user_lastname,
email: data[id].user_email,
phone: data[id].user_phone,
address1: data[id].user_address_1,
address2: data[id].user_address_2,
address3: data[id].user_address_3,
address4: data[id].user_address_4,
group: data[id].user_group,
});
}
this.user = user;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.func();
},
};
</script>
Console log of object user App.vue
Object { id: "3", firstName: "test", lastName: "test", … }
Next I'm injecting it into component. Object inside component exists, but empty - all data cease to exist.
<script>
export default {
inject: ["user"],
};
</script>
console log of object user in component
<empty string>
While in App.vue data is still there, in any components object appears to be empty, but it is there. Any idea why?
Thanks for help.
In short, this happens because you are reassigning user rather than changing user.
Let's say you have a Child component that consumes your inject data and renders the users in a list:
<template>
<div> Child </div>
<ul>
<li v-for="user in users" :key="user.id"> {{user.name}} </li>
</ul>
</template>
<script>
import {inject} from "vue";
export default {
name: "Child",
setup() {
const users = inject("users");
return {users};
}
}
</script>
To provide the users from parent component, all you need to ensure is that users itself is a reactive object, and you keep changing it from the parent rather than reassigning it.
I am going to use the composition api to illustrate what I mean. Compared to options api, everything in composition api is just plain javascript hence there is a lot less behind-the-scene magic. At the end I will tell you how options api is related to the composition api.
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {reactive, provide, toRefs} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const data = reactive({users: ""});
const generateUsers = () => {
// notice here you are REASSIGNING the users
data.users = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(data.users);
}
// this way of provide will NOT work
provide("users", data.users);
// this way works because of toRefs
const {users} = toRefs(data);
provide("users", users);
return {generateUsers};
}
}
</script>
A few things to note:
the data options in the options api is exactly the same as const data = reactive({users: ""}). Vue will run your data() method, from where you have to return a plain object. And then Vue will automatically call reactive to add reactivity to it.
provide, on the other hand, is not doing any magic - neither in options api, nor in the composition api. It just passes whatever it is given to the consuming component without any massaging.
the reason provide("users", data.users) does not work as you would expect is that the way you populate the users is not a change to the same data.users object (which actually is reactive), but a reassign all together.
the reason toRefs works is because toRefs links to the original parent.
With this understanding in mind, to fix your original code, you just need to ensure you change, instead of reassigning, the users. The simplest way is to define user as an array and push into it when you load data. (in contrast to defining it initially as a string and reassigning it later)
P.S. what also works in composition api, and is a lot simpler, is to:
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {ref, provide} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const users = ref();
const generateUsers = () => {
// notice here you are not reassigning the users
// but CHANGING its value
users.value = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(users.value);
}
provide("users", users);
return {generateUsers};
}
}
</script>
I have the following components:
/components/SearchBlogs.vue Search component to filter on blog.title and blog.description.
/components/BlogList.vue Here I list all the Blog items.
SearchBlogs.vue
<template>
<div>
<input type="text" v-model="search" #change="emitSearchValue" placeholder="search blog">
</div>
</template>
<script>
import { EventBus } from '../event-bus.js'
export default {
name: 'SearchBlogs',
data: () => {
return {
search: ''
}
},
methods: {
emitSearchValue() {
EventBus.$emit('search-value', 'this.search')
}
}
}
</script>
BlogList.vue
<template>
<div>
<div v-for="blog in filteredBlogs" :key="blog">
<BlogListItem :blog="blog" />
</div>
</div>
</template>
<script>
import BlogListItem from './BlogListItem'
import { EventBus } from '../event-bus.js'
export default {
name: 'BlogList',
components: {
BlogListItem,
},
data: () => {
return {
blogs: [],
searchvalue: ''
}
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog =>
blog.name.toLowerCase().includes(
this.searchvalue.toLowerCase()
)
)
}
},
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
}),
EventBus.$on('search-value', (search) => {
this.searchvalue = value;
})
}
}
</script>
In another page component Blogs I register both components:
<template>
<div>
<h1>Blog</h1>
<TheSidebar>
<SearchBlogs />
</TheSidebar>
<BlogList/>
</div>
</template>
Can anybody see what's missing here? I want, as soon as the user types something in the search input (from the SearchBlogs.vue component), it start filtering and updating the list.
Look at my solution condesandbox
Here is an explanation:
You don't need to use EventBus. You can communicate with Search Component by v-model, using prop value and emiting updated value from the Input.
Then your Main (List) Component is responsible for all the logic.
It keeps the state of a Search
It keeps the items and filtered Items
Thanks to that your Search Component is very clear and has no data, that means it has very little responsibility.
Please ask questions if I can add something to help you understand 😉
UPDATE:
EventBus is a great addition in some cases. Your case is simple enough, there is no need to add it. Right now your architecture is "over engineered".
When you have added listener on EventBus, on created:hookyou should always remove it while Component is being destroyed. Otherwise you can encounter a trouble with double calling function etc. This is very hard to debug, tryst me I'he been there 😉
Going with my suggestion gives you comfort of "no-need-to-remember-about-this" because Vue is doing it for you.
Hope that help.
Couple of issues but essentially the computed prop filteredData will look like:
computed: {
filteredData() {
return this.experiences.filter(
el => el.category.indexOf(this.search) > -1
);
}
}
Also, used quotes around 'this.search' when passing its value back which made it a string.
Fixed sandbox
https://codesandbox.io/s/reverent-lamarr-is8jz
I'm using quasar in SSR mode and I would like to implement meta data. Quasar has a plugin for meta data and I wanted to use it.
For now my project is quite simple, I have a page product (/product/:id) which display product information.
In the page Product, I use prefetch to fetch Data from a remote API base on the param id.
<template>
<q-page padding>
<div v-if="product">
<h1>{{product.name}}</h1>
<span v-html="product.description"/>
</div>
</q-page>
</template>
<script>
import store from '../store'
export default {
name: 'Product',
async preFetch ({ store, currentRoute }) {
let id = currentRoute.params.id
if (typeof store.getters['product/getProduct'](id) === 'undefined') {
let promise = store.dispatch('product/fetchProduct', id)
await promise.then({})
}
},
computed: {
product () {
return this.$store.getters['product/getProduct'](this.$route.params.id)
}
}
}
</script>
So far everything work find. I have my data setted in the store before rendering. But now I wanted to had a meta title. But in meta how can I have acces to the store and route.params.id.
<script>
export default {
name 'Product',
...
meta: {
title: store.getters['product/getProductMetaTitle'](route.params.id)
}
}
</script>
I'm teaching myself Vue.js 2. My task is to pull a list of posts from the Hacker News API. Upon clicking a list post, a new view is to display some of the data from that specific post.
I'm having a very tough time understanding how to get the REST API data populated in the 2nd view upon routing to the 2nd view, from the 1st view.
(I'm using vue-router and vue-resource(?), and not Vuex (it's such a small application).)
Below are the Item.vue and List.vue. From the List.vue, I'm trying to route to the Item.vue by clicking on a list item. For example, click on a list item called "Guy Has Tough Time with Vue", then open a 2nd view to display a title, score, URL, and comments of the post "Guy Has Tough Time with Vue".
List.vue (creates list, XHR request)
<template>
<div class="list-container">
<h1>List Vue</h1>
<ul class="item-list" v-for="(item, index) in this.items">
<li>
<router-link class="list-item" :to="/views">
{{index + 1}}. {{item.title}}
<div class="points">
Points: {{item.points}}
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'List',
props:[]
data(){
return {
items: []
}
},
mounted: function(){
console.log("created");
this.fetchList();
},
methods: {
fetchList(){
this.$http.get('http://hn.algolia.com/api/v1/search?query=javascript&hitsPerPage=25').then((response) => {
this.items = response.data.hits;
})
}
}
}
Item.vue (Receives item-specific data from List.vue)
<template>
<video id="bgvid" playsinline autoplay loop>
<source src="./src/assets/rad.mp4" type="video/mp4">
<div class="item-container">
<h1>Item Vue</h1>
<div class="post-title"></div>
<div class="post-score"></div>
<div class="post-url"></div>
<ul class="post-comments">
<li class="sngl-comment">
</li>
</ul>
</div>
</video>
</template>
<script>
export default {
name: 'Item',
data(){
return {
item: {}
}
},
mounted: function(){
console.log("created");
},
methods: {
}
}
</script>
Thanks in advance!
The problem is that the first view isn't a direct descendant of the second view so you can't pass data to it through props. I actually wouldn't be using vuex for this, instead I would pass an id through the route for the specific list item and fetch that individual item by the id, as an example:
const View1 = {
template: `<div>
<ul>
<li v-for="item in list"><router-link :to="'view2/'+item.id">{{item.name}}</router-link></li>
</ul>
</div>`,
data() {
return {
list: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 3,
name: 'baz'
}]
}
}
}
const View2 = {
template: `<div>Fetch item {{id}} <br /><router-link to="/">back</router-link></div>`,
created(){
console.log('Fetch data for item '+ this.id);
},
computed:{
id(){
return this.$route.params.id
}
}
}
const routes = [{
path: '/',
component: View1
}, {
path: '/view2/:id',
component: View2
}]
const router = new VueRouter({
routes // short for `routes: routes`
})
var app = new Vue({
router
}).$mount('#app')
Here, I've set the route on View2 to be: view2/:id (see: Dynamic route matching), now in the View2 component I can access that id via this.$route.params.id (which I've put in a computed). Once I've got that id I can simply make an ajax request for the data for the specific item.
And here's the JSFiddle: https://jsfiddle.net/craig_h_411/3hwr6mcd/
Using Vuex
If you are unable to retrieve a record by the specific id for some reason and you don't want to duplicate the call, you will need to share state between non-descendant components and that's where Vuex comes in.
There is a misconception with vuex that it's complicated, but if you only want to share state between a couple of components, it's really quite simple (and it's less than 10kb in size) so there really isn't much use trying to avoid it, all you need to add to your project is:
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = {
state:{
item: {}
},
mutations:{
setItem(state, value){
state.item = value;
}
}
}
export default new Vuex.Store(store)
Then in your app.js (or wherever you have you main Vue instance) you would do something like:
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'
new Vue({
store, // this will be available in components via this.$store
render: h => h(App)
}).$mount('#app')
You will also have to make a few changes, such as committing the state before pushing to the route, adding a computed to get the state from the store and removing the id from the route as it's no longer needed.
I've updated the JSFiddle to show you how that would work: https://jsfiddle.net/craig_h_411/bvswc0kb/
When using the vue-router with .vue files, there is no documented way to pass data from one view/component to another.
Let's take the following setup...
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
let routes = [
{
path: '/page1',
component: require('./views/Posts.vue')
},
{
path: '/page2',
component: require('./views/EditPost.vue')
}
];
let router = new VueRouter({
routes
});
new Vue({
el: '#main',
router
});
Posts.vue:
<template>
<div>
Posts.vue passing the ID to EditPost.vue: {{ postId }}
</div>
</template>
<script>
export default {
data() {
return {
allPostsHere: // Whatever...
}
}
}
</script>
EditPost.vue:
<template>
<div>
EditPost.vue received ID from Posts.vue: {{ receivedId }}
</div>
</template>
<script>
export default {
data() {
return {
receivedId: // This is where I need the ID from Posts.vue
}
}
}
</script>
Please note: It is not possible to receive the ID directly from the EditPost.vue, because it has to be selected from Posts.vue.
Question: How can I pass the ID from one view/component to the other?
A route can only be accessed via a URL and a URL has to be something user can type into the URL bar, therefore to pass a variable from one view component to another you have to use route params.
I assume you have a list of posts in Posts component and want to change page to edit a specific post in EditPost component.
The most basic setup would be to add a link in the post list to redirect to the edit page:
<div v-for="post in posts">
{{ post.title }}
<router-link :to="'/post/' + post.id + '/edit'">Edit</router-link>
</div>
Your routes would look like this:
[
{
path: '/posts',
component: require('./views/Posts.vue'),
},
{
path: '/post/:postId/edit',
component: require('./views/EditPost.vue'),
props: true,
},
]
The props configuration option is just to inform the Router to convert route params to component props. For more information see Passing props to route components.
Then in EditPost you'd accept the id and fetch the post from server.
export default {
props: ['postId'],
data() {
return {
post: null,
}
},
mounted() {
this.fetchPost();
},
methods: {
fetchPost() {
axios.get('/api/post/' + this.postId)
.then(response => this.post = response.data);
},
},
}
After the request has been completed, EditPost has its own copy which it can further process.
Note, that on every post edit and every time you enter the post list, you'll make a request to the server which in some cases may be unnecessary, because all needed information is already in the post list and doesn't change between requests. If you want to improve performance in such cases, I'd advise integrating Vuex into your app.
If you decide to do so, the components would look very similar, except instead of fetching the post to edit via an HTTP request, you'd retrieve it from the Vuex store. See Vuex documentation for more information.
if you don't want the params appear in the URL bar,you can use window.sessionStorage, window.localStorage or vuex.
Before you leave the view, set your parameters and get it after entering the new view.
You can use a prop on the <router-view :my-id="parentStoredId"></router-view> to pass down data present in the app.vue (main component). To change the parent data you need to emit a custom event comprising the value, from the childs (Posts.vue, EditPost.vue).
Another way is the Non Parent-Child Communication.
The way I prefer is Vuex. Even if it require you to learn the usage, it will repay back when the app grows.