Vue.js computed property not updating - javascript

I'm using a Vue.js computed property but am running into an issue: The computed method IS being called at the correct times, but the value returned by the computed method is being ignored!
My method
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
}
The values printed out by the console.log statement are correct, but when I use filteredClasses in template, it just uses the first cached value and never updates the template. This is confirmed by Vue chrome devtools (filteredClasses never changes after the initial caching).
Could anyone give me some info as to why this is happening?
Project.vue
<template>
<div>
<div class="card light-blue white-text">
<div class="card-content row">
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.name" id="filter-name">
<label for="filter-name">Name</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.status" id="filter-status">
<label for="filter-status">Status (PASS or FAIL)</label>
</div>
<div class="col s2 input-field-white inline">
<input type="text" v-model="filter.apkVersion" id="filter-apkVersion">
<label for="filter-apkVersion">APK Version</label>
</div>
<div class="col s4 input-field-white inline">
<input type="text" v-model="filter.executionStatus" id="filter-executionStatus">
<label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>
</div>
</div>
</div>
<div v-for="(klass, classIndex) in filteredClasses">
<ClassView :klass-raw="klass"/>
</div>
</div>
</template>
<script>
import ClassView from "./ClassView.vue"
export default {
name: "ProjectView",
props: {
projectId: {
type: String,
default() {
return this.$route.params.id
}
}
},
data() {
return {
project: {},
filter: {
name: "",
status: "",
apkVersion: "",
executionStatus: ""
}
}
},
async created() {
// Get initial data
const res = await this.$lokka.query(`{
project(id: "${this.projectId}") {
name
classes {
name
methods {
id
name
reports
executionStatus
}
}
}
}`)
// Augment this data with latestReport and expanded
const reportPromises = []
const reportMeta = []
for(let i = 0; i < res.project.classes.length; ++i) {
const klass = res.project.classes[i];
for(let j = 0; j < klass.methods.length; ++j) {
res.project.classes[i].methods[j].expanded = false
const meth = klass.methods[j]
if(meth.reports && meth.reports.length) {
reportPromises.push(
this.$lokka.query(`{
report(id: "${meth.reports[meth.reports.length-1]}") {
id
status
apkVersion
steps {
status platform message time
}
}
}`)
.then(res => res.report)
)
reportMeta.push({
classIndex: i,
methodIndex: j
})
}
}
}
// Send all report requests in parallel
const reports = await Promise.all(reportPromises)
for(let i = 0; i < reports.length; ++i) {
const {classIndex, methodIndex} = reportMeta[i]
res.project.classes[classIndex]
.methods[methodIndex]
.latestReport = reports[i]
}
this.project = res.project
// Establish WebSocket connection and set up event handlers
this.registerExecutorSocket()
},
computed: {
filteredClasses() {
let classes = this.project.classes
const ret = classes && classes.map(klass => {
const klassRet = Object.assign({}, klass)
klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))
return klassRet
})
console.log(JSON.stringify(ret))
return ret
}
},
methods: {
isFiltered(method, klass) {
const nameFilter = this.testFilter(
this.filter.name,
klass.name + "." + method.name
)
const statusFilter = this.testFilter(
this.filter.status,
method.latestReport && method.latestReport.status
)
const apkVersionFilter = this.testFilter(
this.filter.apkVersion,
method.latestReport && method.latestReport.apkVersion
)
const executionStatusFilter = this.testFilter(
this.filter.executionStatus,
method.executionStatus
)
return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter
},
testFilter(filter, item) {
item = item || ""
let outerRet = !filter ||
// Split on '&' operator
filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>
// Split on '|' operator
seg.split("|").map(x => x.trim()).map(segment => {
let quoted = false, postOp = x => x
// Check for negation
if(segment.indexOf("!") === 0) {
if(segment.length > 1) {
segment = segment.slice(1, segment.length)
postOp = x => !x
}
}
// Check for quoted
if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {
if(segment[segment.length-1] === segment[0]) {
segment = segment.slice(1, segment.length-1)
quoted = true
}
}
if(!quoted || segment !== "") {
//console.log(`Item: ${item}, Segment: ${segment}`)
//console.log(`Result: ${item.toLowerCase().includes(segment)}`)
//console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)
}
let innerRet = quoted && segment === "" ?
postOp(!item) :
postOp(item.toLowerCase().includes(segment))
//console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)
return innerRet
}).reduce((x, y) => x || y, false)
).reduce((x, y) => x && y, true)
//console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)
return outerRet
},
execute(methID, klassI, methI) {
this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"
// Make HTTP request to execute method
this.$http.post("/api/Method/" + methID + "/Execute")
.then(response => {
}, error =>
console.log("Couldn't execute Test: " + JSON.stringify(error))
)
},
registerExecutorSocket() {
const socket = new WebSocket("ws://localhost:4567/api/Executor/")
socket.onmessage = msg => {
const {methodID, report, executionStatus} = JSON.parse(msg.data)
for(let i = 0; i < this.project.classes.length; ++i) {
const klass = this.project.classes[i]
for(let j = 0; j < klass.methods.length; ++j) {
const meth = klass.methods[j]
if(meth.id === methodID) {
if(report)
this.project.classes[i].methods[j].latestReport = report
if(executionStatus)
this.project.classes[i].methods[j].executionStatus = executionStatus
return
}
}
}
}
},
prettyName: function(name) {
const split = name.split(".")
return split[split.length-1]
}
},
components: {
"ClassView": ClassView
}
}
</script>
<style scoped>
</style>

If your intention is for the computed property to update when project.classes.someSubProperty changes, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.
This has bitten me when using a Vuex store with en empty state object. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.
I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.
Vue reactiviy docs, for more info:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret.
Official docs.

I had this issue when the value was undefined, then computed cannot detect it changing. I fixed it by giving it an empty initial value.
according to the Vue documentation

I have a workaround for this kind of situations I don't know if you like it. I place an integer property under data() (let's call it trigger) and every time the object that I used in computed property changes, it gets incremented by 1. So, this way, computed property updates every time the object changes.
Example:
export default {
data() {
return {
trigger: 0, // this will increment by 1 every time obj changes
obj: { x: 1 }, // the object used in computed property
};
},
computed: {
objComputed() {
// do anything with this.trigger. I'll log it to the console, just to be using it
console.log(this.trigger);
// thanks to this.trigger being used above, this line will work
return this.obj.y;
},
},
methods: {
updateObj() {
this.trigger += 1;
this.obj.y = true;
},
},
};
here's working a link

If you add console.log before returning, you may be able to see computed value in filteredClasses.
But DOM will not updated for some reason.
Then you need to force to re-render DOM.
The best way to re-render is just adding key as computed value like below.
<div
:key="JSON.stringify(filteredClasses)"
v-for="(klass, classIndex) in filteredClasses"
>
<ClassView
:key="classIndex"
:klass-raw="klass"
/>
</div>
Caution:
Don’t use non-primitive values like objects and arrays as keys. Use string or numeric values instead.
That is why I converted array filteredClasses to string. (There can be other array->string convert methods)
And I also want to say that "It is recommended to provide a key attribute with v-for whenever possible".

You need to assign a unique key value to the list items in the v-for. Like so..
<ClassView :klass-raw="klass" :key="klass.id"/>
Otherwise, Vue doesn't know which items to udpate. Explanation here https://v2.vuejs.org/v2/guide/list.html#key

I have the same problem because the object is not reactivity cause I change the array by this way: arrayA[0] = value. The arrayA changed but the computed value that calculate from arrayA not trigger. Instead of assign value to the arrayA[0], you need to use $set for example.
You can dive deeper by reading the link below
https://v2.vuejs.org/v2/guide/reactivity.html
I also use some trick like adding a cache = false in computed
compouted: {
data1: {
get: () => {
return data.arrayA[0]
},
cache: false
}
}

For anybody else being stuck with this on Vue3, I just resolved it and was able to get rid of all the this.$forceUpdate()-s that I had needed before by wrapping the values I returned from the setup() function [and needed to be reactive] in a reference using the provided ref() function like this:
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'CardDisplay',
props: {
items: {
type: Array,
default: () => []
},
itemComponent: Object,
maxItemWidth: { type: Number, default: 200 },
itemRatio: { type: Number, default: 1.25 },
gapSize: { type: Number, default: 50 },
maxYCount: { type: Number, default: Infinity }
},
setup () {
return {
containerSize: ref({ width: 0, height: 0 }),
count: ref({ x: 0, y: 0 }),
scale: ref(0),
prevScrollTimestamp: 0,
scroll: ref(0),
isTouched: ref(false),
touchStartX: ref(0),
touchCurrentX: ref(0)
}
},
computed: {
touchDeltaX (): number {
return this.touchCurrentX - this.touchStartX
}
},
...
}
After doing this every change to a wrapped value is reflected immediately!

If you are adding properties to your returned object after vue has registered the object for reactivity then it won't know to listen to those new properties when they change. Here's a similar problem:
let classes = [
{
my_prop: 'hello'
},
{
my_prop: 'hello again'
},
]
If I load up this array into my vue instance, vue will add those properties to its reactivity system and be able to listen to them for changes. However, if I add new properties from within my computed function:
computed: {
computed_classes: {
classes.map( entry => entry.new_prop = some_value )
}
}
Any changes to new_prop won't cause vue to recompute the property, as we never actually added classes.new_prop to vues reactivity system.
To answer your question, you'll need to construct your objects with all reactive properties present before passing them to vue - even if they are simply null. Anyone struggling with vues reactivity system really should read this link: https://v2.vuejs.org/v2/guide/reactivity.html

Related

Nothing shows up after setState filling by components

Client: React, mobx
Server: NodeJS, MongoDB
Short question:
I have an array of elements which fills inside of useEffect function, expected result: each element of array should be rendered, actual result: nothing happens. Render appears only after code changing in VSCode.
Tried: changing .map to .forEach, different variations of spread operator in setState(...[arr]) or even without spread operator, nothing changes.
Info:
Friends.jsx part, contains array state and everything that connected with it, also the fill-up function.
const [requestsFrom, setRequestsFrom] = useState([]) //contains id's (strings) of users that will be found in MongoDB
const [displayRequestsFrom, setDisplayRequestsFrom] = useState([]) //should be filled by elements according to requestsFrom, see below
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
console.log(`empty`) //this part of code never executes
return
} else {
const _candidate = await userPage.fetchUserDataLite(f)
_arr.push( //template to render UserModels (below)
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
console.log(_arr)
}
})
setDisplayRequestsFrom(_arr)
// console.log(`displayRequestsFrom:`)
console.log(displayRequestsFrom) //at first 0, turns into 3 in the second moment (whole component renders twice, yes)
}
Render template function:
const render = {
requests: () => {
return (
displayRequestsFrom.map((friendCandidate) => {
return (
<FriendModel link={friendCandidate.link} username={friendCandidate.username} userId={friendCandidate.userId}/>
)
})
)
}
}
useEffect:
useEffect(() => {
console.log(`requestsFrom.length === ${requestsFrom.length}`)
if (!requestsFrom.length === 0) {
return
} else if (requestsFrom.length === 0) {
setRequestsFrom(toJS(friend.requests.from))
if (toJS(friend.requests.from).length === 0) {
const _arr = [...requestsFrom]
_arr.push('0')
setRequestsFrom(_arr)
}
}
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
//displayRequestsFrom and requestsFrom lengths should be same
}
},
[requestsFrom]
)
Part of jsx with rendering:
<div className={styles.Friends}>
<div className={styles['friends-container']}>
{render.requests()}
</div>
</div>
UPD: my console.log outputs in the right order from beginning:
requestsFrom.length === 0
requestsFrom.length === 3
displayRequestsFrom === 0
displayRequestsFrom === 3
As we can see, nor requestsFrom, neither displayRequestsFrom are empty at the end of the component mounting and rendering, the only problem left I can't find out - why even with 3 templates in displayRequestsFrom component doesn't render them, but render if I press forceUpdate button (created it for debug purposes, here it is:)
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
<button onClick={forceUpdate}>force update</button>
PRICIPAL ANSWER
The problem here is that you are executing fetch inside .map method.
This way, you are not waiting for the fetch to finish (see comments)
Wrong Example (with clarification comments)
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom];
// we are not awating requestsFrom.map() (and we can't as in this example, cause .map is not async and don't return a Promise)
requestsFrom.map(async (f) => {
const _candidate = await userPage.fetchUserDataLite(f)
// This is called after setting the state in the final line :(
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
} )
setDisplayRequestsFrom(_arr) // This line is called before the first fetch resolves.
// The _arr var is still empty at the time of execution of the setter
}
To solve, you need to await for each fetch before updating the state with the new array.
To do this, your entire function has to be async and you need to await inside a for loop.
For example this code became
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
for (let f of requestsFrom) {
const _candidate = await fetchUserData(f)
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
}
setDisplayRequestsFrom(_arr)
}
You can also execute every fetch in parallel like this
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
await Promise.all(requestsFrom.map((f) => {
return fetchUserData(f).then(_candidate => {
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
});
}));
setDisplayRequestsFrom(_arr);
}
Other problems
Never Calling the Service
Seems you are mapping on an empty array where you are trying to call your service.
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
/* HERE */ requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
return
If the array (requestsFrom) is empty ( as you initialized in the useState([]) ) the function you pass in the map method is never called.
Not sure what you are exactly trying to do, but this should be one of the problems...
Don't use state for rendered components
Also, you shoudn't use state to store rendered components
_arr.push(
<FriendModel key={_candidate.id} isRequest={true} link='#' username={_candidate.login} userId={_candidate._id}/>
)
, instead you should map the data in the template and then render a component for each element in your data-array.
For example:
function MyComponent() {
const [myData, setMyData] = useState([{name: 'a'}, {name: 'b'}])
return (<>
{
myData.map(obj => <Friend friend={obj} />)
}
</>)
}
Not:
function MyComponent() {
const [myDataDisplay, setMyDataDisplay] = useState([
<Friend friend={{name: 'a'}} />,
<Friend friend={{name: 'b'}} />
])
return <>{myDataDisplay}</>
}
Don't use useEffect to initialize your state
I'm wondering why you are setting the requestsFrom value inside the useEffect.
Why aren't you initializing the state of your requestsFrom inside the useState()?
Something like
const [requestsFrom, setRequestsFrom] = useState(toJS(friend.requests.from))
instead of checking the length inside the useEffect and fill it
So that your useEffect can became something like this
useEffect(() => {
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
}
},
[requestsFrom]
)

How would I be able to return more than one object based on user input amount

Let's say PackInput is an array input.
What I'd like to do's return a dynamic amount of objects depending on how large the input array PackInput is.
For example: if PackInput is [4,5,6], then I'd like three objects returned for each ItemID.
For example: return {... ItemID: 4 ...}, return {... ItemID: 5 ...}, return {... ItemID: 6 ...}.
My current code below is only grabbing the first item of the array instead of all of them and I'm not sure why. I've turned my wheels on this for so long and now I've hit a wall. What am I doing wrong?
for(let i = 0; i < PackInput.length; i++) {
return {
TimestampUTC: Date.now(),
Payload: {
ItemID : PackInput[i]
}
}
}
Updated:
let array = PackInput.map((items) => ({
TimestampUTC: Date.now(),
Payload: {
ItemID : items
}
})
);
let objects = array.reduce(function(target, key, index) {
target[index] = key;
return target;
})
return objects;
You can use the map method to achieve what you want
return PackInput.map((element) => ({
TimestampUTC: Date.now(),
Payload: {
ItemID : element
}
}))
A return statement ends the execution of a function, and returns control to the calling function/upper scope.
Update on object:
const object = PackInput.reduce(function(previousValue, currentValue, index) {
return {
...previousValue,
[index]: currentValue
}
}, {})
You need to provide an empty object as 2nd argument for the reduce function.
You can return an array/object. The problem is that you can call return only once in a function and as soon as a function reaches return it would be returned to the caller scope. You can use the loop to create the array/object and then return the final value:
let array = [];
for(let i = 0; i < PackInput.length; i++) {
array.push({
TimestampUTC: Date.now(),
Payload: {
ItemID : PackInput[i]
}
});
}
return array;

vuetify expansion panel does not update with data change

I have a bunch of expansion panels that are rendered from a list. This list has 60+ items in it, so I have set up a pagination to display 5 results at a time. The problem is that the expansion panels do not correctly update with the new lists. I Know that I am returning a new list.
{{this.viewFilteredTools}}
<success-factors v-for="(x,index) in this.viewFilteredTools" :key="index" :factor="x" :tool="tool" />
{{x}}
</v-expansion-panels>
<v-pagination
v-model="page"
:length="this.paginationLength/5"></v-pagination>
This is just holding the expansion panels which are coming in through <success-factors/>.
<script>
import SuccessFactors from './SuccessFactors.vue';
export default {
components: { SuccessFactors },
props:{
tool:{
type: Object,
required: false,
}
},
data() {
return {
page:1,
paginationLength: 0,
factors: {},
factorsList: [],
filterByItems: [
{text:'A to Z (Complete Reviews First)',value:'ascending'},
{text:'Z to A (Complete Reviews First)',value:'descending'},
{text:'A to Z (all)',value:'allAscending'},
{text:'Z to A (all)',value:'allDescending'}
],
filterValue: {text:'Z to A (all)',value:'allDescending'},
viewFilteredTools:[]
};
},
mounted() {
console.log('something happens here')
this.factorsList = this.tool.factors;
this.paginateResults(1,this.factorsList)
this.paginationLength = this.factorsList.length
},
watch: {
page(oldPage){
this.paginateResults(oldPage,this.factorsList);
}
},
// computed:
async fetch() {
const { $content} = this.$nuxt.context;
this.factors = (await $content(this.tool['factors']).fetch());
this.factorsList = this.tool.factors;
},
methods: {
sortList(lstValue) {
console.log(this.tool.factors);
let sortFactors = this.tool.factors;
sortFactors = sortFactors.sort((a,b) => {
if(a<b){
return -1;
}
if(a>b){
return 1;
}
return 0;
})
this.factors = sortFactors;
},
paginateResults(page,results){
console.log(results)
const startCount = (page-1)*5;
const endCount = startCount + 5;
this.startCount = startCount+1;
this.endCount = endCount;
console.log(results.slice(startCount,endCount))
this.viewFilteredTools = results.slice(startCount,endCount);
}
}
};
</script>
this.viewFilteredTools is created from this.factorsList inside the mount lifecycle. Then any time a new page is chosen it is updated from the method sortList(). I can see the viewFilteredTools does change with every page change due to printing it out above the <success-factors>. However, the <success-factors> does not take the data from the updated lists.
Your best bet is to use a computed property. Vue knows when a computed property is updated & would re-render when that property updates.

test case failing due to .map is not a function error

Hi i have a react component expenses-total.js and a corresponding test case expenses-total.test.js as shown below.
expenses-total.js
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
};
expenses-total.test.js
import selectExpensesTotal from '../../selectors/expenses-total';
const expenses = [
{
id: "1",
description: "gum",
amount: 321,
createdAt: 1000,
note: ""
},
{
id: "2",
description: "rent",
amount: 3212,
createdAt: 4000,
note: ""
},
{
id: "3",
description: "Coffee",
amount: 3214,
createdAt: 5000,
note: ""
}
];
test('Should return 0 if no expenses', ()=>{
const res = selectExpensesTotal([]);
expect(res).toBe(0);
});
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal(expenses[0]);
expect(res).toBe(321);
});
test('Should correctly add up multiple expenses',()=>{
const res = selectExpensesTotal(expenses);
expect(res).toBe(6747);
});
when i run the test case, its getting failed by giving an error
TypeError: expenses.map is not a function
I know the test case is correct but dont know what is wrong with thecomponent.
Could anyone please help me in fixing this error?
The problem is with if (expenses.length === 0) and the test case that uses selectExpensesTotal(expenses[0]):
expenses[0] passes an object, which has no length property, so in the function being tested, expenses.length returns undefined. However, undefined === 0 evaluates to false so your code goes into the else block tries to use .map on the object, which doesn't have that function, thus it throws an error.
In a brief: you can't map over an object.
expenses is an array of objects, so expenses[0] is an object.
Condition expenses.length === 0 evaluates to false, since obviously .length property does not exist on Object.prototype, so the else condition takes place - your function tries to map over an object.
The problem is that expenses[0] is an object (you probably expected it to be an array) and an object does not have a map function. A quick hack would be to add another ifs into the loop to check if expenses is actually an object. So that:
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
if (typeof expenses === 'object') {
return expenses.amount
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
}
};
I hope this help.
To fix this error, you can pass in an array of object into
selectExpensesTotal([expenses[0]])
rather than just an object
selectExpensesTotal(expenses[0])
So your code show look like this:
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal([expenses[0]]);
expect(res).toBe(321);
});
.map function will now work on expenses. Because, this is now an array of object ( works with map function ) and not an object(This does not work with map function)

Vuejs generate elements from object inside components

I'm trying to create a component that will render elements inside VueJs virtual dom with a Vuex state.
The problem is I get this error but I don't understand why and how to fix it
Avoid using observed data object as vnode data: {"class":"btn btn-default"}
Always create fresh vnode data objects in each render!
Inside my Vuex state I store and object where I define the elements properties
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'test',
},
{
type: 'i',
},
],
},
My components code look like
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [],
nestedElements = [];
let parentElementConfig = {};
for (const entry of object) {
let nodeConfig = {};
if (typeof entry.config !== 'undefined' && isNestedElement) {
nodeConfig = entry.config;
} else if (typeof entry.config !== 'undefined') {
parentElementConfig = entry.config;
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
if (!isNestedElement) {
nodeConfig = parentElementConfig;
}
generatedElement.push(createElement(
entry.type,
nodeConfig === {} ? entry.value : nodeConfig,
nestedElements
));
}
if (isNestedElement) {
return generatedElement;
}
return createElement('ul', generatedElement);
},
},
render(createElement) {
const barToolsElements = this.$store.state.titleBar.barToolsElements;
if (barToolsElements) {
return this.iterateThroughObject(barToolsElements, createElement);
}
return false;
},
The error is produced when I try to pass inside my last generatedElement.push() definition.
Because entry.value is {"class":"btn btn-default"}.
I don't understand why it tell me to recreate a fresh Vnode object while this value is used only once.
Did I miss or misunderstand something?
Might be because you're passing references to objects in your store's state, which might lead inadvertently to their mutation. Try creating deep clones of these objects when you pass them around, like for example ..
nodeConfig = JSON.parse(JSON.stringify(entry.config));
parentElementConfig = JSON.parse(JSON.stringify(entry.config));
nodeConfig === {} ? JSON.parse(JSON.stringify(entry.value)) : nodeConfig,

Categories

Resources