Vue - how to add reactive properties to objects in an array of objects? - vue.js

I have an array of objects coming from the backend. I need to add additional data onto each object, to send.
My data coming in looks like this:
let arr = [
{
id: 1,
city: 'Orlando',
},
{
id: 2,
city: 'Miami',
},
{
id: 3,
city: 'Portland',
}
]
When the data comes loaded in through Vue, I need to have those properties be reactive. Meaning vue can see when those values change and call the appropiate lifecycle methods. See https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
let newArr = [
{
id: 1,
city: 'Orlando',
state: 'Fl',
country: 'USA',
},
{
id: 2,
city: 'Miami',
state: 'Fl',
country: 'USA',
},
{
id: 3,
city: 'Portland',
state: 'OR',
country: 'USA',
}
]
You can't just loop through and append properties to each object normally in vanillaJS, so how would you do this?

The vue docs suggests to use Object.assign when appending properties to make them reactive
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
But, this is only if you have to append one object.
For a list of objects, this is how you would initialize the data
newArr = arr.map(item => {
let newItem = Object.assign({}, item, {
state: undefined,
country: undefined,
});
return newItem;
});
Now all your objects properties in the array of objects are reactive

Vue also intercepts calls to Array.push, so you may like this better:
arr.forEach(item => newArr.push(item))

Quite late but thought of providing the answer as it can be useful to someone else in the future:
If you would like to add an additional property to a certain object within an array of objects then you can use something like this:
var searchId = 1
const cityInfo = arr.find(ele => ele.id === searchId)
Vue.set(identifiersNode, 'state', "Karnataka")
Vue.set(identifiersNode, 'country', "India")

Related

Accessing data from array in rendered list in vue.js

I am trying to use a property from a rendered list which may change depending on if a checkbox is filled or not. However, mathSkill and scienceSkill always show 0. I feel like I'm doing something very wrong in trying to access booleanValue but I am not sure what else I could put in the if statement to allow it to update the values. Thank you in advance if you have any insight!
const app = new Vue({
el: '#app',
data: {
abilities: [
{ value: 'math', id: 'math', booleanValue:'no' },
{ value: 'science', id: 'science', booleanValue:'no'},
{ value: 'english', id: 'english', booleanValue:'no'},
],
// VARIABLES
mathSkill: 0,
scienceSkill: 0,
},
computed: {
addToMath: function() {
if (this.abilities[0] === 'yes' )
mathSkill = mathSkill +1,
scienceSkill = scienceSkill + 1;
}
}
I don't know exactly what you are trying to accomplish.
Don't define the variables, if you are going to calculate them.
Use .filter to make a new array based on some condition, and use .length to get how many objects in that array
computed: {
matchSkill() { return this.abilities.filter(ability => ability.booleanValue === "yes").length},
}
Example code

Add object to array in nested json object - Redux

I'm getting a hard time adding an object to an array inside a JSON object.
This is my state:
const DATA =
{
data: [
{
id: 1,
routeName: 'my 2 route',
origin: 'Tel Aviv',
destination: 'Netanya',
date: '25-01-2021',
km: '60',
stops: [
{
id: 0,
address: 'test',
lat: '32.0853',
lon: '34.7818',
customerName: 'test',
tel: '00000',
},
{
id: 1,
address: 'adddress',
lat: '32.0853',
lon: '34.7818',
customerName: 'test',
tel: '00000',
}
],
},
{
id: 2,
routeName: 'my second route',
origin: 'Holon',
destination: 'Hadera',
date: '12-02-2021',
km: '70',
stops: [
{
id: 0,
address: 'address0',
lat: '32.0853',
lon: '34.7818',
customerName: 'customer0',
tel: '00000000',
},
{
id: 1,
address: 'address1',
lat: '32.0853',
lon: '34.7818',
customerName: 'customer1',
tel: '00000000',
},
],
},
],
}
I don't know how to write the reducer, tried few ways but the state doesn't change.
My reducer gets the route id + stop to add this route.
I will be happy for some help here :)
You'll need to find the parent route using the route's id, and then you'll need to create a new stops array by spreading, and adding the new stop.
You can use Array.findIndex() to find the actual route, and the slice the array, and update the route. However, another simple option is to map the data's routes, and update the route with the matching id.
const routeReducer = (state, { type, payload: { routeId, stop } }) => {
switch (type) {
case 'ADD_STOP':
return {
...state,
data: state.data.map(route => route.id === routeId ? {
...route,
stops: [...route.stops, stop]
} : route)
}
}
}
Usually in redux it's better to normalize the state, which makes it easier to update single items.
You could have a reducer that did something like this:
const updateItemInArray = (array, itemId, updateItemCallback) => {
return array.map(item => {
if (item.id !== itemId) return item;
// Use the provided callback to create an updated item
return updateItemCallback(item);
});
};
const data = (state = [], action) => {
switch (action.type) {
case 'ADD_STOP_SUCCESS':
return updateItemInArray(state, action.payload.routeId, (item) => ({...item, stops: [...item.stops, action.payload.stop]}))
default: return state;
}
}
When the action.type 'ADD_STOP_SUCCESS' is called the payload of the action would contain the new stop object you are wanting to add to the state.

Issue with passing the data in Vue / EventBus

I have an issue that I do not understand regarding passing data in Vue.
In store I have the following data (six categories - wine, beer, whiskey, gin, vodka and rum):
state: {
shopItems: [
{
id: 1,
category: "wine",
price: 20,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//Z",
items: [
{
id: 1,
name: "Ruby",
price: 17,
image:
"https://images.freshop.com/1564405684707189696/e52a7445e17de485c0ae890de8762d57_medium.png"
},
{
id: 2,
name: "Sauvignon Blanc",
price: 23,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00898322593368/2e0ad0dbe0ef46eded5590acdf43cae5_large.png&width=256&type=webp&quality=40"
},
{
id: 3,
name: "Dark Horse",
price: 25,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00085000024218/680b13803f3203f3117eb69082f95cc2_large.png&width=256&type=webp&quality=40"
},
{
id: 4,
name: "Andree",
price: 35,
image:
"https://ipcdn.freshop.com/resize?url=https://images.freshop.com/00085000008287/17d49b2b92223defb638ca7b535dbab3_large.png&width=256&type=webp&quality=40"
}
]
},
{
id: 2,
category: "beer",
price: 3,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEhAQEg8TFhEXExYSGRcVFRYSEhYVGBUYGBUVFRYcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/",
items: [
{
id: 1,
name: "Banana",
price: 5,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAQDxIPEhIPFRAPEhcPEBYSEBgSDw8QFRIS0tLS0uLi0tLS8tLS8tLi0tLS0tLi0tLS8tLS0wLS0tLS0tLS0tLSstLf/AABEIAOEA4QMBEQACEQEDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABQYEBwECCAP/"
},
{
id: 2,
name: "Franziskaner Weissbier",
price: 6,
image:
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxIQEhAQEg8TFhEXExYSGRcVFRYSEhYVGBUDEQH/xAAcAAEAAgIDAQAAAAAAAAAAAAAABgcEBQEDCAL/xABIEAACAgECAgcEBwQFCgcAAAAAAQIDEQQhBRIGBxMiMUFRYXGBwRQjMnKRobFCUpLCJDNigtE0Q1 "
},
{
id: 3,
name: "Birra Moretti",
price: 5,
image:
"data:image/jpeg; QQFEiExBgcTIkFRYXGBFCMycpGhsdEkM0JSYsE1Q1OCkhUWJTREVIOTotLh8PH/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAgMEAQUG/8QANBEBAAICAAQDBQcEAgMAAAAAAAECAxEEEiExE0FRBSIyYXGBkaGxwdHwFEJS4SPxFTOC/9oADAMBAAIRAxEAPwDuMBAQEBAQEBAQEBAQNHau1adKoe59wE7q91mLN5BVBJ+ "
}
]
},
.
.
.
In my application I am passing the data related to the index of the chosen category from ToBeBought component to ShowDetails component – using EventBus.
showDetails(item) {
console.log("#item.category");
console.log(item.category);
const chosenCategoryIndex = this.shopItems.findIndex(
i => i.category === item.category
);
// const chosenCategoryItems = this.shopItems[chosenCategoryIndex]
// .items;
console.log("#chosenCategoryIndex");
console.log(chosenCategoryIndex);
EventBus.$emit("chosenCategory", chosenCategoryIndex);
this.$router.push("/details");
I pass the index of the chosen category - e.g. for wine index is 0.
I run chosenCategoryItems method on mounted
(I tried running it on created as well but it did not help)
to get the data in ShowDetails component. In console.log from this method I get an array with detailed data e.g. wines but after that method in mounted I have another console log to show the array (console.log(this.detailedItemsToBeBought);) - I get undefined from it.
It looks like the data was cleared in the meantime - I do not get what is going on here.
I tried to add watcher on detailedItemsToBeBought data but it is not being triggered.
data() {
return {
itemsToBeBought: [],
detailedItemsToBeBought: []
};
},
mounted() {
this.chosenCategoryItems();
console.log("#detailedItemsToBeBought mounted");
console.log(this.detailedItemsToBeBought);
console.log("#this.shopItems[0].items");
console.log(this.shopItems[0].items);
},
methods: {
chosenCategoryItems() {
EventBus.$on("chosenCategory", data => {
const chosenCategoryIndex = data;
console.log("#chosenCategoryIndex");
console.log(chosenCategoryIndex);
this.detailedItemsToBeBought = this.shopItems[
chosenCategoryIndex
].items;
console.log("#detailedItemsToBeBought event bus");
console.log(this.detailedItemsToBeBought);
});
return this.detailedItemsToBeBought;
}
When I try to display the data I do not see anything but when hardcode this.shopItems[0].items I get the data I need - e.g. for wine category the array with wines;
I know it is not an easy issue but is anyone able and willing to take the challenge to explain me whot might had gone wrong here. Why I get the data from this.shopItems[0].items but when I pass index to this.detailedItemsToBeBought = this.shopItems[chosenCategoryIndex].items; - it works fine in the method chosenCategoryItems but after that the data this.detailedItemsToBeBought are undefined on mounted?
I attach screenshot from console.
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/30rk7.png
and the whole repository:
https://github.com/agnesliszka/alcohol_shop
It looks like your component needs to register for the listener BEFORE you emit the event.
You might want to simply store the chosen category in the store, instead of using an event.
If you really want to use an event (not recommended), you can delay the emit until after the component is ready, using nextTick or setTimer.
You will also want to register the listener in created or mounted.
this.$nextTick(()=>{
EventBus.$emit("chosenCategory", chosenCategoryIndex);
})
this.$router.push("/details");

How to store multiple api's data with VueX and add properties to the response

Suppose i want to store data from multile Api requests - when the App is instantiated,
and i also want to add properties to each response
Here is the url of the api 'http://api.open-notify.org/iss-pass.json?lat=LAT&lon=LON'
Updated
I want to instantiated the app with all the data from a couple of get requests - so i'm looping all the requests - that part is works fine , but i also want to be able to add to response.data.response object properties from the cities array, so the finale result will be :
cities: [{
id: 0,
cityName: 'Select a city',
stuff from response.data...
},......
Here is the main part of the Api call
const store = new Vuex.Store({
state: {
satelliteData: [],
dataObj:[],
loading: true,
cities: [{
id: 0,
cityName: 'Select a city',
cityLocation: null
},
{
id: 1,
cityName: 'Tel Aviv',
cityLat: '32.0853',
cityLon: '34.7818'
},
{
id: 2,
cityName: 'London',
cityLat: '51.5074',
cityLon: '-0.1278'
},
{
id: 3,
cityName: 'New York',
cityLat: '40.7128',
cityLon: '-74.0060'
},
],
},
actions: {
loadData({commit}) {
for(var i=0; i<this.state.cities.length; i++){
var currData = this.state.cities[i];
if(!!this.state.cities[i].hasOwnProperty('cityLat'))
axios.get(URL, {
params: {
lat: this.state.cities[i].cityLat,
lon: this.state.cities[i].cityLon,
},
}).then((response) => {
Here is where i want to be able to access the Cities array
/* var cities = this.$store.state.cities;
console.log(cities) */
commit('updateSatelliteData', response.data.response)
commit('changeLoadingState', false)
})
}
}
},
Fiddle
Since i'm new to Vue - i'm sure that there are a couple of mistakes here. Thanks

Transform objects pointfree style with Ramda

Given the function below, how do I convert it to point-free style? Would be nice to use Ramda's prop and path and skip the data argument, but I just can't figure out the proper syntax.
const mapToOtherFormat = (data) => (
{
'Name': data.Name
'Email': data.User.Email,
'Foo': data.Foo[0].Bar
});
One option would be to make use of R.applySpec, which creates a new function that builds objects by applying the functions at each key of the supplied "spec" against the given arguments of the resulting function.
const mapToOtherFormat = R.applySpec({
Name: R.prop('Name'),
Email: R.path(['User', 'Email']),
Foo: R.path(['Foo', 0, 'Bar'])
})
const result = mapToOtherFormat({
Name: 'Bob',
User: { Email: 'bob#example.com' },
Foo: [{ Bar: 'moo' }, { Bar: 'baa' }]
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
Here's my attempt:
const mapToOtherFormat = R.converge(
(...list) => R.pipe(...list)({}),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
const obj = {Name: 'name', User: {Email: 'email'}, Foo: [{Bar: 2}]}
mapToOtherFormat(obj)
Ramda console
[Edit]
We can make it completely point-free:
const mapToOtherFormat = R.converge(
R.pipe(R.pipe, R.flip(R.call)({})),
[
R.pipe(R.view(R.lensProp('Name')), R.set(R.lensProp('Name'))),
R.pipe(R.view(R.compose(R.lensProp('User'), R.lensProp('Email'))), R.set(R.lensProp('Email'))),
R.pipe(R.view(R.compose(R.lensProp('Foo'), R.lensIndex(0), R.lensProp('Bar'))), R.set(R.lensProp('Foo')))
]
)
Ramda console