Vuex: How can I pass a variable to a getter? - vue.js

In a vue app I get an elementId from the URL to pass in to a vuex getter:
<template>
<p>elementId: {{ elementId }}</p>
<p>loadedElement: {{ loadedElement }}</p>
</template>
<script>
export default {
data() {
return {
elementId: this.$route.params.id,
};
},
computed: {
loadedElement() {
return this.$store.getters.getElementById(this.elementId);
},
},
};
</script>
Getter:
getElementById: (state) => (id) => {
return state.elements.find(ele => ele.id === id)
},
Right now the page gets rendered like this:
However, when hardcode an elementId, it works:
<template>
<p>elementId: {{ elementId }}</p>
<p>loadedElement: {{ loadedElement }}</p>
</template>
<script>
export default {
data() {
return {
elementId: 3,
};
},
computed: {
loadedElement() {
return this.$store.getters.getElementById(this.elementId);
},
},
};
</script>
I don't understand what I'm doing wrong since getting the elementId from the route seems to work, but it is not passed into the getter function.
What am I doing wrong?

Most likely this.$route.params.elementId is a string but your element IDs are numbers. Using === to compare a string and number will not match.
Try using == instead, or converting this.$route.params.elementId to a number:
data() {
return {
elementId: +this.$route.params.id,
};
},
(Of course, maybe you want to do more error checking as part of this.)

Related

How to create a method with props passed to child component (Vue3)

I am struggling on the last part of my starter project with outputting the results onto its own component.
I have created a method in the parent component to push the results to an array, and I am then passing that array as props to the child component.
If I just display the array in my child component it works fine, however what I am trying to do is then create a method in my child component based on the results passed via the props.
When I try to do this I am not getting anything outputted, is this something which you can do in Vue?
Parent:
<template>
<button #click="decreaseCount">Decrease</button>
<button #click="increaseCount">Increase</button>
<div class="counterDiv">Counter: {{ count }}</div>
<button #click="calculateResults">Submit</button>
<results v-if="resultsVisible" :results="results"></results>
</template>
<script>
import Results from "./components/Results.vue";
export default {
name: "App",
components: {
Results,
},
data() {
return {
count: 0,
results: [],
resultsVisible: false,
};
},
methods: {
increaseCount() {
this.count += 1;
},
decreaseCount() {
this.count -= 1;
},
calculateResults() {
this.results.push(this.count);
this.resultsVisible = true;
},
},
};
</script>
Child:
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (results < 10) {
this.resultsText = "Below 10";
}
},
},
};
</script>
I have created a very basic example of my problem below
You should emit a custom event from child component which has as handler the parent method:
child :
<template>
<div class="finalResults">Results: {{ this.resultsText }}</div>
</template>
<script>
export default {
props: ["results"],
data() {
return {
resultsText: "",
};
},
methods: {
returnText() {
if (this.results < 10) {
this.resultsText = "Below 10";
this.$emit('push-item', this.resultsText )
}
},
},
};
</script>
in parent component :
<results v-if="resultsVisible" :results="results" #push-item="pushResult"></results>
...
methods:{
pushResult(resulttext){...}
....
}

how to assign a dynamic parameter in an array in the computed method and mapState

hello here is my computed method :
computed:
mapState({
aftermovie: (state) => state.festival.aftermovies[id]
}),
id: {
set() { return this.$route.params.id}
},
if i put state.festival.aftermovies [0] it works but if i try to get id in url, id is undefined, can you help me please
I think the issue is you can't access this or other computed property in mapState, try the following:
computed: {
...mapState({
aftermovies: (state) => state.festival.aftermovies
}),
id() {
return this.$route.params.id
},
aftermovie() {
return this.aftermovies[this.id]
}
}
thank you very much yes indeed we can not access this in mapState here is the functional solution:
data() {
return {
// -1 because the aftermovie index is 1 and the index in the array starts at 0
id: this.$route.params.id - 1
}
},
computed:
mapState({
aftermovies: (state) => state.festival.aftermovies
}),
and finally to access the data
<template>
<div>
<div class="container text-center">
<div>
{{ aftermovies[this.id].id }}
{{ aftermovies[this.id].name }}
{{ aftermovies[this.id].video }}
</div>
</div>
</div>
</template>

why vue v-for doesn't rerender child component when data change?

parent
export default {
name: "App",
components: {ChildComponent},
data: function() {
return {
itemList: []
};
},
methods: {
fetchData() {
callApi().then(res => { this.itemList= res; })
}
}
};
<template>
<ol>
<template v-for="(item) in itemList">
<li :key="item.id">
<child-component :item="item"></card>
</li>
</template>
</ol>
</template>
child
export default {
name: "ChildComponent",
props: {
item: { type: Object }
},
data: function() {
const {
name,
address,
.......
} = this.item;
return {
name,
address,
......
};
},
};
</script>
child get the item props which is an object.
I'm confused why the itemList point to another array, but the child doesn't update?
is it because key doesnt change? (but other data changed..)
thanks
It is because of this part of your code:
const {
name,
address,
} = this.item;
return {
name,
address,
};
What it does it copies name and address from item and return them.
It happens only once in your code while component is created.
If after that your prop item change, your data doesn't copy it and still returns the first values.
Solution:
If you don't change name or address in a child component, just use a prop
this.item.name in a script or {{ item.name }} in a template
It is already reactive so your component will update when prop changes.
Not sure what you do in your child component but this is enough to do and should react to your parent component changes. Basically the idea is to use the prop. I made some tweaks also.
export default {
name: "App",
components: {ChildComponent},
data: function() {
return {
itemList: []
};
},
methods: {
fetchData() {
callApi().then(res => { this.itemList= res; })
}
}
};
<template>
<ol>
<li v-for="(item) of itemList" :key="item.id">
<card :item="item"></card>
</li>
</ol>
</template>
export default {
name: "ChildComponent",
props: {
item: {
type: Object,
required: true
}
}
};
</script>
<template>
<div>
{{ item.name }} {{ item.address }}
</di>
</template>
It is because the data function is called only once on the creation of the component.
The recommended way to get data that depend on other data is to use computed.
export default {
name: "ChildComponent",
props: {
item: { type: Object }
},
computed: {
name() {
return this.item.name;
},
address() {
return this.item.address;
}
}
};
https://v2.vuejs.org/v2/guide/computed.html#Basic-Example

Replace tag dynamically returns the object instead of the contents

I'm building an chat client and I want to scan the messages for a specific tag, in this case [item:42]
I'm passing the messages one by one to the following component:
<script>
import ChatItem from './ChatItem'
export default {
props :[
'chat'
],
name: 'chat-parser',
data() {
return {
testData: []
}
},
methods : {
parseMessage(msg, createElement){
const regex = /(?:\[\[item:([0-9]+)\]\])+/gm;
let m;
while ((m = regex.exec(msg)) !== null) {
msg = msg.replace(m[0],
createElement(ChatItem, {
props : {
"id" : m[1],
},
}))
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
}
return msg
},
},
render(createElement) {
let user = "";
let msg = this.parseMessage(this.$props.chat.Message, createElement)
return createElement(
'div',
{
},
[
// "hello",// createElement("render function")
createElement('span', '['+ this.$props.chat.Time+'] '),
user,
msg,
]
)
}
};
</script>
I thought passing createElement to the parseMessage method would be a good idea, but it itsn't working properly as it replaces the tag with [object object]
The chatItem looks like this :
<template>
<div>
<span v-model="item">chatITem : {{ id }}</span>
</div>
</template>
<script>
export default {
data: function () {
return {
item : [],
}
},
props :['id'],
created() {
// this.getItem()
},
methods: {
getItem: function(){
obj.item = ["id" : "42", "name": "some name"]
},
},
}
</script>
Example :
if the message looks like this : what about [item:42] OR [item:24] both need to be replaced with the chatItem component
While you can do it using a render function that isn't really necessary if you just parse the text into a format that can be consumed by the template.
In this case I've kept the parser very primitive. It yields an array of values. If a value is a string then the template just dumps it out. If the value is a number it's assumed to be the number pulled out of [item:24] and passed to a <chat-item>. I've used a dummy version of <chat-item> that just outputs the number in a <strong> tag.
new Vue({
el: '#app',
components: {
ChatItem: {
props: ['id'],
template: '<strong>{{ id }}</strong>'
}
},
data () {
return {
text: 'Some text with [item:24] and [item:42]'
}
},
computed: {
richText () {
const text = this.text
// The parentheses ensure that split doesn't throw anything away
const re = /(\[item:\d+\])/g
// The filter gets rid of any empty strings
const parts = text.split(re).filter(item => item)
return parts.map(part => {
if (part.match(re)) {
// This just converts '[item:24]' to the number 24
return +part.slice(6, -1)
}
return part
})
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<template v-for="part in richText">
<chat-item v-if="typeof part === 'number'" :id="part"></chat-item>
<template v-else>{{ part }}</template>
</template>
</div>
If I were going to do it with a render function I'd do it pretty much the same way, just replacing the template with a render function.
If the text parsing requirements were a little more complicated then I wouldn't just return strings and numbers. Instead I'd use objects to describe each part. The core ideas remain the same though.

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.