Vue - component props not watching object changes properly - javascript

I have two objects in my root - obj and newObj. I watch changes on my obj object with deep: true, and on changes I update newObj accordingly.
In my vue debugger, the newObj seems updated as expected, however the component doesn't perform the for loop count. Or if I try to {{ newObj }}, it logs only the first update.
I tried to re-create the issue on this Fiddle.
my html:
<div id="app">
<button #click="appendTo">Append</button>
<my-comp v-bind:new-obj="newObj"></my-comp>
</div>
and vue:
new Vue({
el: '#app',
data: {
obj: {},
newObj: {}
},
methods: {
appendTo() {
if (typeof this.obj[1] === 'undefined') {
this.$set(this.obj, 1, {})
}
var randLetter = String.fromCharCode(Math.floor(Math.random() * (122 - 97)) + 97);
this.$set(this.obj[1], randLetter, [ [] ])
}
},
watch: {
obj: {
handler(obj) {
var oldKeys = Object.keys(obj)
var newKeys = Object.keys(this.newObj);
var removedIndex = newKeys.filter(x => oldKeys.indexOf(x) < 0 );
for (var i = 0, len = removedIndex.length; i < len; i++) {
delete this.newObj[removedIndex[i]]
}
oldKeys.map((key) => {
if (this.newObj.hasOwnProperty(key)) {
var newInnerKeys = Object.keys(this.newObj[key]);
var oldInnerKeys = Object.keys(obj[key]);
var additions = oldInnerKeys.filter(x => newInnerKeys.indexOf(x) < 0);
for (var i = 0, len = additions.length; i < len; i++) {
// here
this.$set(this.newObj[key], additions[i], [ [] ]);
}
var deletions = newInnerKeys.filter(x => oldInnerKeys.indexOf(x) < 0);
for (var i = 0, len = deletions.length; i < len; i++) {
delete this.newObj[key][deletions[i]]
}
} else {
this.newObj[key] = {}
for (var innerKey in obj[key]) {
this.$set(this.newObj, key, {
[innerKey]: [ [] ]
});
}
}
console.log(obj);
console.log(this.newObj)
});
},
deep: true
}
}
})
Vue.component('my-comp', {
props: ['newObj'],
template: `
<div>
<div v-for="item in newObj">
test
</div>
</div>
`
})

Your data newObj has a getter and setter defined by vue. When the setter is called, the UI is re-rendered. The setter is triggered when you change the reference of newObj, not when you change its value. I mean:
this.newObj = {} // triggered
this.newObj['key'] = 'value' // not triggered
You can add a deep watcher on the property this.newObj. Or change its reference with a trick:
this.newObj = Object.assign({}, this.newObjec);
which create a copy of the object newObject.
Here is the fiddle updated.
https://jsfiddle.net/749nc5d2/

Related

Trouble with array and state

Can someone explain, why I'm getting an error, when I'm removing items from an array? It works once, but then it crashes. Checked - is boolean meaning.
removeCards = () => {
console.clear();
for (let i = 0; i < this.state.cards.length; i++) {
console.log(this.state.cards[i]);
if (this.state.cards[i].checked) {
delete this.state.cards[i];
}
}
this.setState({ cards: this.state.cards });
};
In React, state is immutable. So instead of trying to alter it directly, create a copy of it and then apply that to state -
removeCards = () => {
console.clear();
const newCards = [];
for (let i = 0; i < this.state.cards; i++) {
if (!this.state.cards[i].checked) {
newCards.push(this.state.cards[i];
}
}
this.setState({ cards: newCards });
};
Maybe my trouble in that? That's how i'm chaning states from true to false and in reverse.
myFunc = (props) => {
let num = Number(props);
num--;
let cards = [...this.state.cards];
if (this.state.cards[num].checked) {
cards[num] = { ...cards[num], checked: false };
} else {
cards[num] = { ...cards[num], checked: true };
}
this.setState({ cards });
};

How to dispatch a Vue computed property

I´m trying to dispatch an object which is created in a computed.
I can´t get it to work as I´m fairly new to vue.js
I want to dispatch the object "updateObject" to the vuex-store.
Tried with setters but didn´t work. I think if I can set the "varia" object to the same object like "updateObject" then I could maybe dispatch it?
Hope somebody can help me.
Here is my code:
<template>
<div class="detail">
<b-row align-v="center"><b-button variant="success" #click="submit()">submit</b-button></b-row>
// some more code...
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
var ergebnis = {}
res.forEach(key => {
if (this.FA[key]) {
ergebnis[key] = this.FA[key]
}
})
return ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
</script>
It tell´s me "this.ergebnis" is undefined
You can try it declaring "ergebnis" as global variable under data as
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null,
ergebnis : {}
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
res.forEach(key => {
if (this.FA[key]) {
this.ergebnis[key] = this.FA[key]
}
})
return this.ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
Now ergebnis is accessible

TypeError: Cannot read property 'style' of undefined

export class EstimateForm extends React.Component<IEstimateFormProps,
IEstimateFormState> {
state: IEstimateFormState = {
cellUpdateCss: 'red',
toRow: null,
fromRow: null,
estimateList: null,
estimateItemList: [],
poseList: null,
levelList: null,
partList: null,
selectedEstimate: null,
totalEstimateItems: 0,
selectedIndexes: [],
totalEstimateAmount: 0,
grid: null,
projectId: 0,
};
constructor(props, context) {
super(props, context);
this.state.estimateList = this.props.estimateList;
}
rowGetter = i => {
const row = this.state.estimateItemList[i];
const selectRevison = this.state.selectedEstimate.revision;
if (row['pose.poseName']) {
const poseCode =
row['pose.poseName'].substring(row['pose.poseName'].lastIndexOf('[') + 1,
row['pose.poseName'].lastIndexOf(']'));
for (const pose of this.state.poseList) {
if (pose.poseCode === poseCode) {
row.pose = pose;
}
}
}
if (row['level.levelName']) {
const levelCode = row['level.levelName'].substring(
row['level.levelName'].lastIndexOf('[') + 1,
row['level.levelName'].lastIndexOf(']')
);
for (const level of this.state.levelList) {
if (level.levelCode === levelCode) {
row.level = level;
}
}
}
if (row['level.part.partName']) {
const partCode = row['level.part.partName'].substring(
row['level.part.partName'].lastIndexOf('[') + 1,
row['level.part.partName'].lastIndexOf(']')
);
for (const part of this.state.partList) {
if (part.partCode === partCode) {
row.part = part;
}
}
}
row.get = key => eval('row.' + key);
row.totalCost = (row.materialCost + row.laborCost) * row.amount;
const changeColor = {
backgroundcolor: 'red'
};
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
return row;
}
}
handleGridRowsUpdated = ({ fromRow, toRow, updated }) => {
const rows = this.state.estimateItemList.slice();
for (let i = fromRow; i <= toRow; i++) {
const rowToUpdate = rows[i];
const updatedRow = update(rowToUpdate, { $merge: updated });
rows[i] = updatedRow;
}
this.setState({ estimateItemList: rows, fromRow: (fromRow), toRow: (toRow)
}, () => {
});
};
saveEstimateItems = () => {
if (this.state.selectedEstimate == null) {
toast.warn(<Translate
contentKey="bpmApp.estimateForm.pleaseSelectEstimate">Please select an
estimate</Translate>);
return;
}
render() {
return ()
}
I wanna to change the row color when the condition row.revision > this.state.selectedEstimate.revision . How can I prevent the change of this.color. However TypeError: Cannot read property 'style' of undefined get error but row color is not change. how can i solve this problem it is my first project in react and i dont know where is the problemThanks for your feedback guys.
Okay, so without the rest of the context because your pasted code is difficult to read and understand, the simplest reason for your issue is in this chunk:
const all = document.getElementsByClassName('react-grid-Row') as
HTMLCollectionOf<HTMLElement>;
debugger; if (row.revision > selectRevison) {
for (let i = this.state.fromRow; i <= this.state.toRow; i++) {
all[i].style.color = 'red'; //HERE
}
Essentially there's multiple things that could go wrong here, but most likely there are either no rows with that class on the page, or less than your this.state.fromRow, I see you've got the debugger in there, but you are missing a few things:
You aren't doing a null check on all to make sure you are finding something
You aren't checking whether all.length > this.state.fromRow
You aren't breaking the for loop if all.length < this.state.toRow
It's failing because all[i] doesn't exist, or there's no values:
all = [0, 1]
and you are looking for all[3] for example
Throw in those fallbacks and check what all is on page load and you should be able to figure it out.

refreshing component on get method with vuejs

I Have a component which parse a json file on mounted phase.
The problem is :
when I click on a button , a send another GET method to get another json file and transmit it to my compoment.
the problem is that the component don't reload itself with the new props and my component display the old values
If someone know how to refresh components , here is my code
<template>
<div class="perturbo">
<div class="col-md-3 btnMenu">
<button v-for="item,index in perturboButtonData.button_list" type="button"
v-bind:style="'background:white'"
class="btn-lg btn-block myBtnClass"
#click="activeButton(index)">
{{item.label}}
</button>
</div>
<div class="col-md-9">
<div class="row">
<graphe
v-for="ana,index in perturboData.ANA"
:key="ana.id"
:data="ana"
:index="index"
type="number"
:timeSpec="perturboData.liste_dates">
</graphe>
</div>
<div class="row">
<graphe
v-for="dig,index in perturboData.DIG"
:key="dig.id"
:index="index"
:data="dig"
type="number"
isDigit="YES"
:timeSpec="perturboData.liste_dates">
</graphe>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import Graphe from './Graphe/Graphe.vue'
export default {
name: 'perturbo',
components : {
'graphe' : Graphe
},
data: function () {
return {
isActivated: false,
perturboData: {},
perturboButtonData: {}
}
},
methods: {
activeButton : function (index) {
console.log(this.perturboButtonData)
axios.get('./static/cgi/' + this.perturboButtonData.button_list[index].link)
.then((response) => {
this.perturboData = response.data;
this.isActivated = true
})
}
},
mounted : function () {
axios.get('./static/cgi/format_json_perturbo.json')
.then((response) => {
this.perturboButtonData = response.data;
})
}
}
</script>
Here is the code of my graphe component
<template>
<div class="graphe">
<vue-chart
:chart-events="chartEvents"
:columns="columns"
:rows="rows"
chart-type="LineChart"
:options="options">
</vue-chart>
</div>
</template>
<script>
export default {
name: 'graphe',
props: {
data: {},
timeSpec : Array,
index: Number,
type: String,
isDigit:String,
},
data: function () {
return {
chartEvents: {
'select': function() {
},
'ready': function() {
}
},
rows: [],
columns: [],
options: {
title: this.data.name,
hAxis: {
},
vAxis: {
ticks : []
},
width: 650,
height: 350,
curveType : 'function'
}
}
},
methods: {
normaliseData : function () {
for (let i = 0; i < this.timeSpec.length; i++) {
this.rows[i] = []
this.rows[i][0] = parseFloat(this.timeSpec[i])
}
this.columns[0] = { 'type': 'number', 'label': 'time' }
for (let i = 0; i < this.data.data.length; i++){
this.columns[i+1] = {'type': this.type ,'label': this.data.data[i].name}
}
for (let i = 0; i < this.timeSpec.length; i++) {
for (let y = 0; y < this.data.data.length; y++) {
this.rows[i][y+1] = parseFloat(this.data.data[y].data[i])
}
}
if (this.isDigit == "YES"){
this.digRow(this.rows)
for (let v = 0; v < this.data.data.length; v ++){
this.options.vAxis.ticks[v] = { v: v, f: this.data.data[v].name}
}
this.options.curveType = ''
}
},
digRow : function (rowTab) {
let newRow = []
let lengthMax = rowTab.length
let rowTmp = []
let index = 0
for (let i = 0; i < lengthMax; i ++){
rowTmp[index] = []
rowTmp[index][0] = rowTab[i][0]
for(let y = 1; y < rowTab[i].length; y ++){
rowTmp[index][y] = rowTab[i][y] + y - 1
}
if (i + 1 !== rowTab.length)
{
newRow = rowTmp[index].slice()
newRow[0] = rowTab[i+1][0]
rowTmp.splice(index+1,0,newRow)
index = index + 2
}
}
this.rows = rowTmp
}
},
mounted: function () {
// // pour les colones
this.normaliseData()
}
}
</script>
EDIT : I know where the problem is :
The data received from the parent is treated just once on the mounted function ! , that's why it doesn't reload on change
Should I use a watcher on props ? how can I do that
Instead of using a method to normalize the data, use a computed property for your rows, columns and options properties. This way, it will update automatically if any of the dependent properties change.
For example, your options property could be a computed property that looks like this:
computed: {
options() {
let options = {
title: this.data.name,
hAxis: {
},
vAxis: {
ticks : []
},
width: 650,
height: 350,
curveType : 'function'
};
if (this.isDigit == "YES"){
this.digRow(this.rows)
for (let v = 0; v < this.data.data.length; v ++){
options.vAxis.ticks[v] = { v: v, f: this.data.data[v].name}
}
options.curveType = ''
}
return options;
}
}
Now, whenever this.data, this.isDigit, or this.rows changes, the options property will update as well.
Your rows and columns properties would look like this:
rows() {
let rows = [];
for (let i = 0; i < this.timeSpec.length; i++) {
rows[i] = []
rows[i][0] = parseFloat(this.timeSpec[i])
for (let y = 0; y < this.data.data.length; y++) {
rows[i][y+1] = parseFloat(this.data.data[y].data[i])
}
}
return rows;
},
columns() {
let columns = [];
columns[0] = { 'type': 'number', 'label': 'time' }
for (let i = 0; i < this.data.data.length; i++) {
columns[i+1] = {'type': this.type ,'label': this.data.data[i].name}
}
return columns;
},
Your changed property won't force view update
To react to state changes, it’s usually better to use a computed
property or watcher.
Try this variant
watch: {
timeSpec(){
//do something
//this.normaliseData()
}
}

Javascript Array to Object

I have an array that looks like so:
files = [
'Dashboard/Logs/Errors',
'Dashboard/Logs/Other',
'Accounts/Main',
]
I want to make it look like this:
navigation = [
{
"title": "Dashboard",
"dropdown": [
{
"title": "Logs",
"dropdown": [
{
"title": "Errors",
},
{
"title": "Other",
}
]
}
]
},
{
"title": "Accounts",
"dropdown": [
{
"title": "Main",
}
]
}
]
I have the following so far:
var navigation = [];
for (var i = 0; i < files.length; i++) {
var parts = files[i].split('/');
navigation.push({title: parts[0]});
for (var j = 1; j < parts.length; j++) {
}
}
I am having difficulties figuring out a decent way to do this. What I have so far already doesn't work because it creates two objects under navigation each with title: "Dashboard". Any ideas for a clever approach? Thanks :)
This should produce the desired output:
var files = [
'Dashboard/Logs/Errors',
'Dashboard/Logs/Other',
'Accounts/Main',
];
var navigation = [];
// Iterates through a navigation array and returns the object with matching title, if one exists.
var getNavigationObject = function(nav, title) {
for (var i = 0; i < nav.length; i++) {
if (nav[i].title == title) {
return nav[i];
}
}
};
// Adds a file to the nav.
// The input is an array of file components (i.e. file.split('/'))
// This works by recursively adding each component of a file.
var addToNav = function (nav, components) {
var n = getNavigationObject(nav, components[0]);
if (!n) {
n = {
title: components[0]
};
nav.push(n);
}
if (components.length > 1) {
n.dropdown = n.dropdown || [];
addToNav(n.dropdown, components.slice(1));
}
};
// Actually call `addToNav` on each file.
files.forEach(function(e) {
addToNav(navigation, e.split('/'));
});
// Produces the result in string form.
JSON.stringify(navigation, null, 2)
This works by recursively checking if a given element already matches the component of the file. If it does, it recurs into that component's "dropdown". Otherwise, it creates it.
This is an approach with a temporary object and some array methods with no search overhead.
var files = ['Dashboard/Logs/Errors', 'Dashboard/Logs/Other', 'Accounts/Main'],
navigation = function (data) {
var r = [], o = {};
data.forEach(function (a) {
var s = r;
a.split('/').reduce(function (p, b) {
if (p.children) {
p.value.dropdown = p.value.dropdown || [];
s = p.value.dropdown;
p = p.children;
}
if (!(b in p)) {
p[b] = { value: { title: b }, children: {} };
s.push(p[b].value);
}
return p[b];
}, o);
});
return r;
}(files);
document.write('<pre>' + JSON.stringify(navigation, 0, 4) + '</pre>');

Categories

Resources