Passing a dictionary to Vue.js store - javascript

I have a store in my vue.js product where I want to add dictionaries under this format:
axios.post(`http://127.0.0.1:8000/api/${this.endpoint}/`, fd, axiosConfig)
.then(res => {
let newPictureUrl = res.data.picture_1
let newPictureId = res.data.id
let addPicDic = {"url": newPictureUrl, "id": newPictureId}
this.addPicture(addPicDic) })
This is my store.js:
export const store = {
state: {
pictures: []
},
addPicture(newPicture) {
this.state.pictures.push(newPicture);
}
};
But whenever I want to render my pictures in a template, it doesn't work and {{ storeState.pictures }} would return [ "[object Object]" ].
As far as I understand, I get an object 'Object' instead of a 'Dictionary' so how can I pass my datas to the store so I can access them in other templates?

So looking at that code, it looks like you are trying to directly modify the store. You should change the state of the store via mutations. In an ideal setup, your component would have a method that would dispatch an action in the store. This asynchronous action would call the endpoint and then commit with a mutation.
state: {
pictures: []
},
mutations: {
addPicture(state, newPicture) {
state.pictures.push(newPicture)
}
}
Consult mutations and actions in the documentation here.

My mistake, I had to change this.addPicture() for store.addPicture():
axios.post(`http://127.0.0.1:8000/api/${this.endpoint}/`, fd, axiosConfig)
.then(res => {
let newPictureUrl = res.data.picture_1
let newPictureId = res.data.id
let addPicDic = {"url": newPictureUrl, "id": newPictureId}
store.addPicture(addPicDic) })

Note that store.state.pictures is an array of objects.
If you want to render all images, you can use v-for. It will iterate over the array.
<template>
<div>
<img v-for="pic in storeState.pictures" :key="pic.id" :src="pic.url">
</div>
</template>
OR
If you want to display just information (id, urls),
<template>
<div>
<ul>
<li v-for="pic in storeState.pictures" :key="pic.id" > ID: {{pic.id}} | URL: {{pic.url}} </li>
</ul>
</div>
</template>
I assumed that storeState is a computed property which is mapped to the your store.state
Thanks!

Related

VueJS2: How to update objects in an array and pass back to parent?

I have a parent component that is passing down some API data to a child component in order to pre-populate some input fields. When the user changes some of this data on the child component, that child component emits the data back to the parent where we will process it for server submission on user form submit.
To handle the updates for processing, I am sending the child data back as an object which the parent stores in an array (array of objects). This array is what I am sending to the server for processing.
I am struggling with how to update object properties in an array of objects.
Codesandbox: https://codesandbox.io/s/romantic-mestorf-yc0i1h?file=/src/components/Parent.vue
Let me explain in detail. I have 3 components:
<App>
<Parent>
<Child />
</Parent>
</App>
App.vue:
<template>
<div id="app">
<form #submit.prevent="submitForm()">
<Parent
:localShortNames="formValues.localShortNames"
#save-form-data="saveFormData"
/>
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
import Parent from "./components/Parent.vue";
import data from "./assets/data.json"; // <--- will be an actual API request
export default {
components: {
Parent,
},
data() {
return {
formValues: {
localShortNames: data,
},
};
},
methods: {
saveFormData(x) {
// TO DO
},
submitForm() {
// TO DO: save data to server
},
},
};
</script>
Parent.vue:
<template>
<div>
<h5>List of Data</h5>
<Child
v-for="item in localShortNames"
:key="item.localSnameID"
:localShortName="item"
#save-form-data="saveFormData"
/>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
props: {
localShortNames: {
type: Array,
},
},
components: {
Child,
},
data() {
return {
newLocalShortNamesArr: this.localShortNames,
};
},
methods: {
saveFormData(x) {
let elementId = (el) => el.localSnameID === x.localSnameID;
const newArr = this.newLocalShortNamesArr.map((obj) => {
if (elementId) {
// I need to update the existing object in newLocalShortNamesArr with updated user submitted properties
// ...
} else {
// item does not exist, lets push it to newLocalShortNamesArr
// TO DO LATER: create "add new data" button for adding new objects to array
},
},
},
},
}
</script>
Child.vue:
<template>
<div>
<label for="name-input">Name:</label>
<input
type="text"
id="name-input"
v-model="formValues.name"
#input="$emit('save-form-data', formValues)"
/>
<label for="dialect-input">Dialect:</label>
<input
type="text"
id="dialect-input"
v-model="formValues.iso6393Char3Code"
#input="$emit('save-form-data', formValues)"
/>
</div>
</template>
<script>
export default {
props: {
localShortName: {
type: Object,
},
},
data() {
return {
formValues: {
localSnameID: this.localShortName
? this.localShortName.localSnameID
: null,
name: this.localShortName ? this.localShortName.name : null,
iso6393Char3Code: this.localShortName
? this.localShortName.iso6393Char3Code
: null,
},
};
},
};
</script>
Question: How to handle the update of objects in an array and "overwrite" those properties (name, and iso6393Char3Code) if the same id exists in the original array?
In the parent.vue, I was thinking of doing something like this, but I don't know:
saveFormData(x) {
// console.log(x);
let elementId = (el) => el.localSnameID === x.localSnameID;
const newArr = this.newLocalShortNamesArr.map((obj) => {
if (elementId) {
// I need to update the existing object in newLocalShortNamesArr with updated user submitted properties
// ...
} else {
// item does not exist, lets push it to newLocalShortNamesArr
// ...
}
});
Would Object.assign be better here vs map()? All I am trying to do is provide an array to the API called localShortNames that contains all the objects whether they have been updated or not. Hope this makes sense!
I have a codesandbox here with the above code: https://codesandbox.io/s/romantic-mestorf-yc0i1h?file=/src/components/Parent.vue
The first problem in Parent Component in saveFormData
if you want to check if the object exist in array or not you can use findIndex() method it loop through array and return the object index if exists else return -1 if not exist
if exist objectIndex will be greater than -1 and update that index with the object from child component and then we emit the array after updated to App.vue component
saveFormData(x) {
const objectIndex = this.newLocalShortNamesArr.findIndex((ele) => {
return ele.localSnameID == x.localSnameID
});
if (objectIndex > -1) {
// the object exists
this.newLocalShortNamesArr[objectIndex] = { ...x };
} else {
// this case need new html form for add new item in array
this.newLocalShortNamesArr.push(x);
}
this.$emit("save-form-data", this.newLocalShortNamesArr);
},
in App.vue component
we update the main array with the data that emitted from parent component
saveFormData(x) {
this.formValues.localShortNames = [...x];
},
i updated the codesandbox with the new code ..
I hope the solution will be clear to you
https://codesandbox.io/s/dreamy-clarke-v04wfl?file=/src/App.vue:595-734

vue: how to filter data from array

I am new with Vue and I am using API to show data, here in my code when click is performed to category it saves that name of category and redirects user to another page called category.vue, my problem is after redirecting user I want now to show questions with that category name that been received only (as filter), is there a way to do it?
/* this is how i saved data to transfer to category page */
<div class="category" v-for="(categoryy,index) in category(question1)" :key="index">
<router-link :to="`/category/${category(question1)}`"> {{ categoryy }} </router-link>
</div>
category.vue
<template>
<div class="vw">
<p> related question of </p>
<p>{{ this.$route.params.cat }}</p> /* category name i sent appears here */
<ul class="container-question" v-for="(question1,index) in questions" :key="index"
>
/* THE PROBLEM : it shows all questions without filtering */
{{question1.question}}
</ul>
</div>
</template>
<script>
export default {
props:
{
question1: Object
},
data(){
return{
questions: []
}
},
computed:{
category(){
let category = this.$route.params.cat;
return category
}
},
mounted: function(){
fetch('https://opentdb.com/api.php?amount=10&category=9&difficulty=medium&type=multiple',{
method: 'get'
})
.then((response) => {
return response.json()
})
.then((jsonData) => {
this.questions = jsonData.results
})
}
}
</script>
You can first get all the categories with this route https://opentdb.com/api_category.php
As i suspect you already have done on the first vue page.
Then as you do, you pass the id of the category as a route param.
The only thing you seem to miss here is that you have hardcoded the category id 9 in your mounted fetch.
You would want to change this to:
fetch(`https://opentdb.com/api.php?amount=10&category=${this.category}&difficulty=medium&type=multiple`,{
method: 'get'
})
This way uses the computed property you created above from the router param from the previous page.
you have to pass the route param not in a computed property, but directly on the mounted hook,
export default {
mounted() {
fetch(
`https://opentdb.com/api.php?amount=10&category=${this.$route.params.cat}&difficulty=medium&type=multiple`,
{
method: "get",
}
)
.then((response) => response.json())
.then((jsonData) => (this.questions = jsonData.results));
},
};

How to use function that return value inside a template? Vuex, Vue

I'm using a template to get data of a json file, I use "v-for" to print all data, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ item.date }}</li>
<ul>
</template>
</div>
`,
But I need use functions, year() to modificate this information and return and result, for example:
template: /*html*/
`
<div class="col-lg-8">
<template v-for="item of actividades">
<ul>
<li>{{ year(item.date) }}</li>
<ul>
</template>
</div>
`,
The value {{ item.date }} print "2021-01-20" but I hope print "2021" using the function {{ year(item.date) }}
Code function year() using javascript:
year(date){
return String(date).substr(0, 4);
}
I tried use that code but is not working, appear this error:
That's my javascript code:
//VueEx
const store = new Vuex.Store({
state: {
actividades: [],
programas: [],
year: ""
},
mutations: {
llamarJsonMutation(state, llamarJsonAction){
state.actividades = llamarJsonAction.Nueva_estructura_proveedor;
state.programas = llamarJsonAction.BD_programas;
},
yearFunction(state, date){
state.year = String(date).substr(8, 2);
return state.year;
}
},
actions: {
llamarJson: async function({ commit }){
const data = await fetch('calendario-2021-prueba.json');
const dataJson = await data.json();
commit('llamarJsonMutation', dataJson);
}
}
});
//Vue
new Vue({
el: '#caja-vue',
store: store,
created() {
this.$store.dispatch('llamarJson');
}
});
Inside a template you can use functions defined as methods or computed. Technically, you can also use data to pass a function to the template, but I wouldn't recommend it. Not that it wouldn't work, but Vue makes anything declared in data reactive and there's no point in making a function (which is basically a constant) reactive. So, in your case:
new Vue({
el: '#app',
data: () => ({
actividades: [
{ date: '2021-01-20' },
{ date: '2020-01-20' },
{ date: '2019-01-20' },
{ date: '2018-01-20' },
{ date: '2017-01-20' }
]
}),
methods: {
year(date) { return date.substring(0, 4); }
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="(item, key) in actividades" :key="key">
{{ year(item.date) }}
</li>
</ul>
</div>
If, for some reason, you want to pass year as computed:
computed: {
year() { return date => date.substring(0, 4); }
}
But it's a convoluted construct (a getter function returning an inner arrow function) and this complexity doesn't serve any purpose. I'd recommend you use a method in your case, since it's the most straight-forward (easy to read/understand).
If you're importing the year function from another file:
import { year } from '../helpers'; // random example, replace with your import
// inside component's methods:
methods: {
year, // this provides `year` imported function to the template, as `year`
// it is equivalent to `year: year,`
// other methods here
}
Side notes:
there is no point in iterating through <template> tags which contain <ul>'s. You can place the v-for directly on the <ul> and lose the <template> (You should only use <template> when you want to apply some logic - i.e: a v-if - to a bunch of elements without actually wrapping them into a DOM wrapper; another use-case is when you want its children to be direct descendants of the its parent: for <ul>/<li> or <tbody>/<tr> relations, where you can't have intermediary wrappers between them). In your case, placing the v-for on the <ul> produces the exact same result with less code.
you should always key your v-for's: <ul v-for="(item, key) in actividades" :key="key">. Keys help Vue maintain the state of list elements, keep track of animations and update them correctly
I see you are trying to work with the Vuex store. And using mutation inside the template syntax.
Not sure if we can call mutation directly via HTML as the way you are doing. In the past when I tried to call a mutation, I would either:
Create an action which would commit that mutation and call that action wrapped inside a method through Vue, something like this:look for a method printSampleLog() that I defined here
Vue.component('followers', {
template: '<div>Followers: {{ computedFollowers }} {{printSampleLog()}}</div>',
data() {
return { followers: 0 }
},
created () {
this.$store.dispatch('getFollowers').then(res => {
this.followers = res.data.followers
})
},
computed: {
computedFollowers: function () {
return this.followers
}
},
methods:{
printSampleLog(){
this.$store.dispatch('sampleAction').then(res => {
this.followers = res.data.followers
})
}
}
});
const store = new Vuex.Store({
actions: {
getFollowers() {
return new Promise((resolve, reject) => {
axios.get('https://api.github.com/users/octocat')
.then(response => resolve(response))
.catch(err => reject(error))
});
},
sampleAction(context){
context.commit('sampleMutation');
}
},
mutations: {
sampleMutation(){
console.log("sample mutation")
}
}
})
const app = new Vue({
store,
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<followers></followers>
</div>
Else create method w/o action in your Vue component committing the mutation directly, using this.$store.commit()
PS: Would recommend creating action around the mutation first, as it is a much cleaner approach.

How to access my state of array in another router page VUEJS, VUEX

I made a page with two routes one is the home page another is the config where you can decide what should be written to that container, now in the config panel I was able to get the input values, I put them to my state with map actions now I am getting an array with string values in it.
How can I access that array with mapGetters ? I link my code:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{ message }}</h1>
</div>
</body>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
export default {
name: "Home",
data() {
return {
// message: this.store.state.message
elementVisible: true
};
},
computed: {
...mapGetters(["message", "sec"]),
...mapGetters({
message: "message",
sec: "sec"
}),
createdDate() {
return moment().format("DD-MM-YYYY ");
},
createdHours() {
return moment().format("HH:mm ");
}
},
mounted() {
this.$store.dispatch("SET_MESSAGE");
this.$store.dispatch("SET_SEC");
setTimeout(() => (this.elementVisible = false), this.sec);
}
};
</script>
so what I have to do is to put to that{{message}} template my message which I received from the config panel and which is in my state right now sitting there as an array of string, for example, ["hello", "how are you"] that's how they are sitting there, so how can I grab the first one('hello') and write it out as a clean string and not as ["hello"] if you know how to grab all of them would be even better.
(RightNow it's just putting that whole array to my template)
Maybe I should something rewrite in my storejs file?
STOREJS:
const state = {
message: [],
// console.log(message);
sec: +[]
// other state
};
const getters = {
message: state => {
// console.log(this.state.message);
return state.message;
},
sec: state => {
return state.sec;
}
// other getters
};
const actions = {
setMessage: ({ commit, state }, inputs) => {
commit(
"SET_MESSAGE",
inputs.map(input => input.message)
);
return state.message;
},
setSec: ({ commit, state }, inputs) => {
commit("SET_TIMEOUT", inputs.map(x => x.sec).map(Number));
console.log(inputs.map(x => x.sec).map(Number));
return state.sec;
}
// other actions
};
const mutations = {
SET_MESSAGE: (state, newValue) => {
state.message = newValue;
},
SET_TIMEOUT: (state, newSecVal) => {
state.sec = newSecVal;
}
// other mutations
};
export default {
state,
getters,
actions,
mutations
};
what my homepage should do is that it writes out that message and there is a sec value which stands for the timeout, after that I want to continue with the second value in that array and when that times out I should want the third to be written out.. and so on.
Thank you!
Hello and welcome to Stack Overflow! Your message Array is being mapped correctly with mapGetters, but you're flattening it as a String when you put it inside the template with {{message}}, since the template interpolation logic covert objects to strings, the same as calling Array.toString in this case. You need to iterate it, i.e. using the v-for directive:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">
<span v-for="m of message" :key="m">{{m}}</span>
</h1>
</div>
</body>
</template>
Of course, if you only need the first item, you could show it directly using:
<template>
<body>
<div class="container">
<h1 v-show="elementVisible" class="info">{{message[0] || 'No message'}}</h1>
</div>
</body>
</template>

Using a method to get array to iterate through with a V-for loop, but nothing is being displayed

I am making a personal project to learn how Vue.js and Express communicate together. I managed to get the desired data from the server by passing a prop in the Vue template (brand of watch to be fetched from the server) as an argument to a method that fetches the data.
This method is being called in the V-for declaration and returns an array of objects. Now, when I test this with a standard array that I instantiated inside the data function all is well, and the data displays just fine. The method definitely fetches the data because I can see it inside Vue devtools and when I print it to the console. So it is there, but for some reason it doesn't want to play nice with V-for.
Here's the code:
<template>
<div class="wrapper">
<div class="product" v-for="(product, index) in getProductsByBrand(brand)" :key="index">
<div class="product-name">
{{ product }}
</div>
</div>
</div>
</template>
<script>
import ProductService from '#/services/ProductService'
export default {
name: 'product',
props: [
'brand'
],
data () {
return {
products: [],
shoppingItems: [
{ name: 'apple', price: '7' },
{ name: 'orange', price: '12' }
]
}
},
methods: {
async getProductsByBrand (brand) {
const response = await ProductService.fetchProductsByBrand(brand)
this.products = response.data.product
console.log(this.products)
console.log(this.shoppingItems)
return this.products
}
When I replace the getProductsByBrand() method with the shoppingItems array it all works. That method returns another array, so I don't see why V-for is having trouble with displaying that data.
Any help is much appreciated!
Vue templates are rendered by a render function, and it's a normal sync function. Therefore you can't call an async function in the template. A better pattern for your code would be as follows:
Template:
<div class="product" v-for="(product, index) in products" :key="index">
Script:
methods: {
async getProductsByBrand (brand) {
const response = await ProductService.fetchProductsByBrand(brand)
return response.data.product
}
},
async created() {
this.products = await this.getProductsByBrand(this.brand);
}
It amounts to the same thing but loads the data into the products data property during the created lifecycle hook.

Categories

Resources