Initialize array data from array prop in a Vue component - javascript

In my Vue component I have an array (of objects) prop called selectedSuppliers. I want to initialize a data prop called suppliers to selectedSuppliers, but any subsequent changes to suppliers should not propogate to selectedSuppliers.
I tried the following
props: {
selectedSuppliers: {
type: Array,
required: true
},
},
data () {
return {
selected: [...this.selectedSuppliers],
}
}
But it doesn't work. What is the correct way to initialize an array data property to an array prop?

If selectedSuppliers is an array of object, then the spread operator only performs a shallow copy of this array. It means that updating any object of suppliers will update the content of selectedSuppliers.
You can take a look at this short post.

This might work,
props: {
selectedSuppliers: {
type: Array,
required: true
},
},
data () {
return {
selected: this.selectedSuppliers.map(o => Object.assign({}, o)
}
}

Related

watch: true for nested objects - NuxtJS (Vue), latest version

I have a form with many fields attached to a data - this.myData:
data: function() {
return {
isDataChanged: false,
myData: {},
myChangedData: {
default: '',
default1: {},
default2: []
},
}
},
myData is populated from a response from the server and it populates the form values.
myChangedData is for the new values, which are changed v-on:input="onChangeMyData($event, 'default')":
onChangeMyData(e, name, required = false){
const val = e.target.value.trim();
this.myChangedData[name] = val;
console.log(this.myChangedData)
this.checkIsmyDataChanged();
},
I can use the same method, providing a key as a second param. With the method checkIsmyDataChanged I am checking is it changed some field in the form. This method loops through myChangedData and compares its properties with changedData and if there is a difference this.isDataChanged = true.
The problem is that, I have a complicated structure of mydata/mydatachanged. default1 has objects in it and default1 is an array of objects. This means that, I can't use onChangeMyData, but other methods with different checks (validations) and now I need to call in all of them this.checkIsmyDataChanged();.
I created a watch for myChangedData:
watch:{
myChangedData: {
handler: function (newVal) {
console.log('change')
},
deep: true
},
},
, but it doesn't execute on change data
Did you try with Vue.set ? Source
Change this.myChangedData[name] = val; to
this.$set(this.myChangedData, 'name', val)
Thanks to that, the modification on the object should be detected and execute the watcher.

update dynamic object in redux using spread operator

I am trying to update an object in redux using spread operator but I am not being able to.
Initial state is an empty object because category is received dynamically from api call.
pages and data are both objects which i want to update using spread operator (or whatever works best)
state = {
[category]: {
pages: {
key: value(array)
},
data: {
key: value(array)
}
}
}
At my reducer I try to update it like this
return {
...state,
[category]: {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
},
};
but i get "error: TypeError: Cannot read property 'pages' of undefined"
What am I doing wrong and how can I update them correctly?
Because state.category is undefined when you fetch this category for the first time. You can fix it like that:
return {
...state,
[category]: state.category
? {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
}
: {
pages,
data: doctors,
total,
},
};

Selecting specific object inside array of objects throwing undefined in Vuejs /Java app

In this app im building i have a little problem referring to adress the SPA to the selected item by its id.
Fetching my data to my end point(action) i got a array of objects , then through a getter i just grab all that data, and using the function find() i filter all the array throwing the object which contains the id referrig to the selected one.Lets say:
First- Exposing on the router the link and the condition of the id which demark the object to select
{
path: '/game/selected/:gameCrabId',
name: 'GameSelectedView',
props:true,
component: GameSelectedView,
},
Second- Eexposing the component where in i access through router link the supposed object which contains the requested id
<v-btn router-link :to="`/game/selected/${game.game_id}`" >Access</v-btn>
Third-Initializing the view in general of the supposed selected object
<template>
<v-container v-if="allGames">
<!-- <div>{{gameSelected}}</div> -->
</v-container>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
export default {
name: "GameSelectedview",
props: ["gameCrabId"],
data() {
return {};
},
// props:{SelectedGameComponent:Object},
methods: {
...mapActions(["allGames"])
},
computed: {
...mapGetters(["getSelectedGame"]),
gameSelected() {
return this.getSelectedGame(this.gameCrabId);
}
},
async created() {
await this.allGames;
await this.gameSelected;
await console.log(this.gameSelected);
},
</script>
then on my state managment component (Vuex) , i trigger the method getter which eventually brings me once i click that button of step 2 , the object which has the same id than the required one, but first im exposing the state where in that array of objects is
state: {
allGamesData: {all_games:[
{"game_id": 1,"game_player": "Romi","game_score": 0},
{"game_id": 2,"game_player": "Messi","game_score": 0},
{"game_id": 3,"game_player": "CR7","game_score": 0},
]
},
},
getters:{
getSelectedGame: (state) => (gameCrabId) => {
console.log(gameCrabId);
return state.allGamesData.all_games.find((gameID) => {
gameID.game_id === gameCrabId;
});
},
}
And this is the getter which eventuallly accesses the state and that array of objects , and using a double arrow function first aims to the state ,and second through the parameter (gameCrabId)which is the once that expose the id so neccesary to complete the link to that selected item;then returning the access to that array of objects in my state , i just use the function find() to establish a comparison between that id carried by the parameter (gameCrabId) and the array of objects brought from the state , breaking the cycle once find the first comparable item in the objects which match with that id gameID.game_id === gameCrabId
In order to see if ,my id carrier works i set a log , and works , the number is brought , but for any reason the filter find() throws me undefined in the object selected despite of already have the id once the comparison get settled, being imposible retrieve any information of that object
Missing return from find()
The callback to Array.prototype.find() must return a Boolean, but currently it returns nothing, so the result of find() would always be null:
return state.allGamesData.all_games.find((gameID) => {
gameID.game_id === gameCrabId; // FIXME: No return statement
});
Mismatched types
game_id is a Number, but gameCrabId is a string, and getSelectedGame() uses === to compare the two, which requires the operands to have the same type. One solution is to perform explicit type conversion to ensure both operands are Number:
getters: {
getSelectedGame: state => gameCrabId => {
return state.allGamesData.all_games.find((gameID) => {
// return gameID.game_id === gameCrabId; // DON'T DO THIS
return gameID.game_id === Number(gameCrabId);
});
}
}
demo

Delete a json inside another in React

Imagine that I have this kind of JSON object on my react state:
this.state={
parent:{
childs:[
child1:{
},
child2:{
},
child3:null,
(...)
]
}
}
to delete the child1 I did the following method:
deleteChild1 = (index,test) => {
const childs= [...this.state.parent.childs];
childs[index] = {
...childs[index],
child1: null
}
this.setState(prevState => ({
...prevState,
parent: {
...prevState.parent,
childs: [
...childs,
]
}
}))
}
This works with no problem, but imagine that I have 100 childs, I have to do 100 methods like this but instead putting the child1 to null I have to put the child100, child99, you get the idea.
My question is that is another way to put the variable null.
Thanks!
Currently your data structure isn't valid so its hard to write up a solution. If I try to create that exact state object it raises an exception Uncaught SyntaxError: Unexpected token : This is because you have an array written like an object. So first thing to do is adjust your state model to be valid syntax
this.state = {
parent: {
childs: {
child1: {
},
child2: {
},
child3: null,
...
}
}
}
Now, what you are describing / what you want to do is a dynamic key reference.
You can do this like so
const childTarget = 'child1'
childs = {
...childs,
[childTarget]: null
}
so to abstract that concept a little more. make it a function parameter
deleteChild = (childTarget) => {
then when you want to remove any particular child you can let them pass their value to this function
<Child onRemove={this.deleteChild} name={name} />
// name here would be like 'child1'
// assuming you are looping over state and rendering a child component for each item
and when the child calls this function
this.props.onRemove(this.props.name)
The answer is very simple, this would be my approach.
Create a function which updates your state with the expect result (removing that property).
If you wish to assign null that you can replace .filter() with a .map() solution. Typically if you are removing a piece of data it does not make sense to null it, but to remove it.
FYI your property childs is an array you have an object within, so you need a list of objects to fix this.
E.g.
[
{
name: "child1"
},
{
name: "child2"
},
{
name: "child3"
}
]
removeChild = (child) => {
const newChildList = this.state.parent.childs.filter(({name}) => name !== child);
this.setState(previousState => ({
...previousState,
parent: {
...previousState.parent,
childs: newChildList
}
}));
}
The key part here is that the data is being updated and overriding the original array. Because you have nested data structure we don’t want to delete any pre-existing data (hence the spreading).
Call the function however you want and if your childs array has an object with the property called name that matches the child function argument, it will be not be present on the next re-render.
Hope this helps.

Prevent prop from overwriting the data

I'm new to vue.js and struggling with the following scenario.
I send an array filled with objects via props to my router-view.
Inside one of my router-view components I use this array in multiple functions, reference it with 'this.data' and safe it inside the functions in a new variable so I don't overwrite the actual prop data.
However the functions overwrite the original prop data and manipulate the data of the prop.
Here is an abstract example of my question:
App.vue
<template>
<div>
<router-view :data='data'></router-view>
</div>
</template>
<script>
export default {
data: function() {
return {
data: [],
};
},
created: function() {
this.getData();
},
methods: {
getData: function() {
this.data = // array of objects
},
}
route component:
<script>
export default {
props: {
data: Array,
},
data: function() {
return {
newData1 = [],
newData2 = [],
}
}
created: function() {
this.useData1();
this.useData2();
},
methods: {
useData1: function() {
let localData = this.data;
// do something with 'localData'
this.newData1 = localData;
}
useData2: function() {
let localData = this.data;
// do something with 'localData'
this.newData2 = localData;
}
}
}
</script>
The 'localData' in useData2 is manipulated from changes in useData1, whereby I don't overwrite the data prop.
Why do I overwrite the prop and how can i prevent it?
The problem you're experiencing a side effect of copying this.data by reference, rather than value.
The solution is to use a technique commonly referred to as cloning. Arrays can typically be cloned using spread syntax or Array.from().
See below for a practical example.
// Methods.
methods: {
// Use Data 1.
useData1: function() {
this.newData1 = [...this.data]
},
// Use Data 2.
useData2: function() {
this.newData2 = Array.from(this.data)
}
}
#Arman Charan is right on his answer. Object and arrays are not primitive types but reference.
There is an awesome video explanation here => JavaScript - Reference vs Primitive Values/ Types
So for reference types you first have to clone it on another variable and later modify this variable without the changes affecting the original data.
However for nested arrays and objects in high level the spead and Array.from will not work.
If you are using Lodash you can use _.cloneDeep() to clone an array or an object safely.
I like functional programming and I use Lodash which I strongly recommend.
So you can do:
let original_reference_type = [{ id:1 }, { id: 2 }]
let clone_original = _.cloneDeep(original_reference_type)
clone_original[0].id = "updated"
console.log(original_reference_type) //[{ id:1 }, { id: 2 }] => will not change
console.log(clone_original) // [{ id: "updated" }, { id: 2 }]
Suggestion: For simple arrays and objects use:
Objects:
let clone_original_data = {...original_data} or
let clone_original_data = Object.assign({}, original_data)
Arrays:
let clone_original_data = [...original_data] or
let clonse_original_data = original_data.slice()
For complex and high nested arrays or Objects go with Lodash's _.cloneDeep()
I think this is most readable, "declarative" way:
First, install lodash npm i lodash. Then import desired function, not the whole library, and initialize your data with array from props.
<script>
import cloneDeep from 'lodash/cloneDeep'
export default {
props: {
data: Array
},
data () {
return {
// initialize once / non reactive
newData1: cloneDeep(this.data),
newData2: cloneDeep(this.data)
}
}
}
</script>

Categories

Resources