Access to props inside javascript - javascript

I just start to learn vuejs 2 but I have an issue, I'm passing a props to a child component like this :
<div class="user">
<h3>{{ user.name }}</h3>
<depenses :user-id="user.id"></depenses>
</div>
And here is how I use it in my child component :
export default {
name: 'depenses',
props: ['userId'],
data () {
return {
depenses: []
}
},
mounted: function () {
this.getDepenses()
},
methods: {
getDepenses: function () {
this.$http.get('myurl' + this.userId).then(response => {
this.depenses = response.body
this.haveDepenses = true
}, response => {
console.log('Error with getDepenses')
})
}
}
}
this.userId is undefined but I'm able to display it with <span>{{ userId }}</span> and I can see in the vuejs console the param userId with the value
Why I have an undefined value in the js ?

Ok I found why I couldn't get my value, thanks to #Saurabh comment, he reminds me that I get my props from an async way, so I had to set a watcher when my props change, like this :
watch: {
userId: function () {
this.getDepenses()
}
},

Related

Issue when trying to interact with an API in Vuejs?

datalist.js
import axios from "axios";
export const datalist = () => {
return axios.get("myapiurl/name...").then((response) => response);
};
HelloWorld.vue
<template>
<div>
<div v-for="item in items" :key="item.DttID">
<router-link
:to="{
name: 'UserWithID',
params: { id: item.DepaD },
query: { DepaD: item.DepaID },
}"
>
<div class="bt-color">{{ item.DepaName }}</div>
</router-link>
</div>
<br /><br /><br />
<User />
</div>
</template>
<script>
import User from "./User.vue";
import { datalist } from "./datalist";
export default {
name: "HelloWorld",
components: {
User,
},
data() {
return {
items: datalist,
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
<template>
<div>
<div v-for="(item, key) in user" :key="key">
{{ item.Accv }}
</div>
</div>
</template>
<script>
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
lists: datalist,
};
},
computed: {
user: function () {
return this.lists.filter((item) => {
if (item.DepaD === this.$route.params.id) {
return item;
}
});
},
},
};
</script>
Error with the code is,
[Vue warn]: Error in render: "TypeError: this.lists.filter is not a function"
TypeError: this.lists.filter is not a function
The above error i am getting in User.vue component in the line number '20'
From the api which is in, datalist.js file, i think i am not fetching data correctly. or in the list filter there is problem in User.vue?
Try to change the following
HelloWorld.vue
data() {
return {
items: [],
};
},
mounted() {
datalist().then((r) => {
this.items = r.data;
});
},
User.vue
data() {
return {
lists: []
};
},
mounted() {
datalist().then((r) => {
this.lists = r.data;
});
},
At least this suppress the error, but i cant tell more based on your snippet since there are network issues :)
Since your datalist function returns a Promise, you need to wait for it to complete. To do this, simply modify your component code as follows:
import { datalist } from "./datalist";
export default {
name: "User",
data() {
return {
// empty array on initialization
lists: [],
};
},
computed: {
user: function() {
return this.lists.filter((item) => {
if (item.DeploymentID === this.$route.params.id) {
return item;
}
});
},
},
// asynchronous function - because internally we are waiting for datalist() to complete
async-mounted() {
this.users = await datalist() // or datalist().then(res => this.users = res) - then async is not needed
}
};
now there will be no errors when initializing the component, since initially lists is an empty array but after executing the request it will turn into what you need.
You may define any functions and import them, but they wont affect until you call them, in this case we have datalist function imported in both HelloWorld and User component, but it did not been called in User component. so your code:
data() {
return {
lists: datalist,
};
},
cause lists to be equal to datalist that is a function, no an array! where .filter() should be used after an array, not a function! that is the reason of error.
thus you should call function datalist and put it's response in lists instead of putting datalist itself in lists
Extra:
it is better to call axios inside the component, in mounted, created or ...
it is not good idea to call an axios command twice, can call it in HelloWorl component and pass it to User component via props

Vue not reacting to a computed props change

I am using the Vue composition API in one of my components and am having some trouble getting a component to show the correct rendered value from a computed prop change. It seems that if I feed the prop directly into the components render it reacts as it should but when I pass it through a computed property it does not.
I am not sure why this is as I would have expected it to be reactive in the computed property too?
Here is my code:
App.vue
<template>
<div id="app">
<Tester :testNumber="testNumber" />
</div>
</template>
<script>
import Tester from "./components/Tester";
export default {
name: "App",
components: {
Tester,
},
data() {
return {
testNumber: 1,
};
},
mounted() {
setTimeout(() => {
this.testNumber = 2;
}, 2000);
},
};
</script>
Tester.vue
<template>
<div>
<p>Here is the number straight from the props: {{ testNumber }}</p>
<p>
Here is the number when it goes through computed (does not update):
{{ testNumberComputed }}
</p>
</div>
</template>
<script>
import { computed } from "#vue/composition-api";
export default {
props: {
testNumber: {
type: Number,
required: true,
},
},
setup({ testNumber }) {
return {
testNumberComputed: computed(() => {
return testNumber;
}),
};
},
};
</script>
Here is a working codesandbox:
https://codesandbox.io/s/vue-composition-api-example-forked-l4xpo?file=/src/components/Tester.vue
I know I could use a watcher but I would like to avoid that if I can as it's cleaner the current way I have it
Don't destruct the prop in order to keep its reactivity setup({ testNumber }) :
setup(props) {
return {
testNumberComputed: computed(() => {
return props.testNumber;
}),
};
}

Make a reactive component with vuejs

I need a Vue component to show some HTML content in v-data-table from Vuetify. I have seen this post Vue 2 contentEditable with v-model, and I created a similar code shown below.
My problem is the component is not reactive. When I click the "Test button", no content is updated in HtmlTextArea.
<template>
<div>
<v-btn #click="doTest()">Test Button</v-btn>
<HtmlTextArea
v-model="content"
style="max-height:50px;overflow-y: scroll;"
></HtmlTextArea>
</div>
<template>
export default {
name: "ModelosAtestados",
components: { HtmlTextArea },
data: () => ({
content: "",
}),
methods: {
doTest() {
this.content = "kjsadlkjkasfdkjdsjkl";
},
},
};
//component
<template>
<div ref="editable" contenteditable="false" v-on="listeners"></div>
</template>
<script>
export default {
name: "HtmlTextArea",
props: {
value: {
type: String,
default: "",
},
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs.editable.innerHTML = this.value;
},
methods: {
onInput(e) {
this.$emit("input", e.target.innerHTML);
},
},
};
</script>
This occurs because HtmlTextArea sets the div contents to its value prop only in the mounted lifecycle hook, which is not reactive.
The fix is to setup a watcher on value, so that the div contents are updated to match whenever a change occurs:
// HtmlTextArea.vue
export default {
watch: {
value: {
handler(value) {
this.$refs.editable.innerHTML = value;
}
}
}
}
demo
In the #click event binder, you have to pass a function. You passed the result of an executed function.
To make it work: #click="doTest" or #click="() => doTest()".
How to debug such problems:
Display the value you want to update on your template to check if its updated: {{content}}
Use the vue devtool extension to check the current state of your components

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

Why can't I assign properties directly into my data?

I can't access my question props to assign its property directly into my data properties. Using the property directly from my template works, but can't assign the property into the data.
I can only access the value of props from my template:
props: ['question'],
data () {
return {
}
};
computed: {
upVote () {
return this.question.upvoted
},
count () {
return this.question.vote_count
}
}
methods: {
upVoteIt () {
if(User.loggedIn()) {
this.upVote = !this.upVote
this.upVote ? this.incr() : this.decr()
}
},
incr () {
this.count ++
},
decr () {
this.count --
}
}
<v-icon size="50" :color="upcolor" #click="upVoteIt">fas fa-sort-up</v-icon>
<span> {{ count }} </span>
I get the value if I use the props directly inside my template instead of reassigning into the data property.
You can use .sync modifier. It is a special feature that you can use when you want synchronize props with your parent component : Here is the documentation . you will no longer use the data.
So how it works, you need in your child compoment emit an event update question with the updated value :
props: ['question'],
methods: {
upVoteIt () {
if(User.loggedIn()) {
this.$emit('update:question', {
...this.question,
upvoted: !this.question.upvoted,
count: this.question.upvoted ? this.question.count - 1 : this.question.count + 1
}
}
}
}
<v-icon size="50" :color="upcolor"
#click="upVoteIt">fas fa-sort-up</v-icon>
<span> {{ question.vote_count }} </span>
Now in your parent you need to do something like this :
<YourQuestionComponent
:question.sync="myQuestionVariable"
/>

Categories

Resources