unexpected side effect vue - javascript

I am a newbie in vue.js. I have a problem with side effect in computed property. I'm not sure why i'm getting an unexpected side effect in computer property error with the code below. ESlint shows me this error in console. I found what does it mean, but I dont have any idea how to change. Any ideas?
export default {
name: "Repaid",
components: {
VueSlideBar
},
data() {
return {
response: {},
slider: {
lineHeight: 8,
value: 2000,
data: []
},
days: {},
monthCounts: [5],
currentMonthCount: 5,
isLoading: false,
errorMessage: "",
validationError: ""
};
},
computed: {
finalPrice() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index]; =>this is my side effect
const payment = this.response.prices[this.days]
[this.slider.value].schedule[0].amount;
}
},
I read that i should add slide() to my variable that.days, to mutate the original object. Or maybe I should carry them to the methodes.
EDIT
I carried all my calculations to methods and trigger a function in computed property.
Now it looks like this:
methods: {
showPeriod() {
const index = this.monthCounts.indexOf(this.currentMonthCount);
this.days = Object.keys(this.response.prices)[index];
const payment = this.response.prices[this.days][this.slider.value]
.schedule[0].amount;
return "R$ " + payment;
},
}
computed: {
finalPrice() {
if (!this.response.prices || this.monthCounts === []) {
return "";
}
return this.showPeriod();
}
},

It's coming from "eslint-plugin-vue" and the link for that rule is below,
https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html
Either you override this rule in your eslint rules file or you can simply turn off the eslint for this specific line like below
this.days = Object.keys(this.response.prices)[index]; // eslint-disable-line
--
One more thing (Not related to your question) is that you need to return some value in computed.

Related

Pinia|Vue3 I can't access the property of the object that returned from the Pinia action

first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']

Vue.js - watch particular properties of the object and load data on change

I have Vue component with prop named product, it is an object with a bunch of properties. And it changes often.
export default {
props: {
product: {
type: Object,
default: () => {},
},
},
watch: {
'product.p1'() {
this.loadData()
},
'product.p2'() {
this.loadData()
},
},
methods: {
loadData() {
doApiRequest(this.product.p1, this.product.p2)
}
},
}
The component should load new data when only properties p1 and p2 of product are changed.
The one approach is to watch the whole product and load data when it is changed. But it produces unnecessary requests because p1 and p2 may not have changed.
Another idea is to watch product.p1 and product.p2, and call the same function to load data in each watcher.
But it may happen that both p1 and p2 changed in the new version of the product, it would trigger 2 calls.
Will it be a good solution to use a debounced function for data load?
Or rather use single watcher for the whole product and compare new p1 and p2 stringified with their old stringified versions to determine if data loading should be triggered?
There are several approaches to this, each with pros and cons.
One simple approach I do is to use a watch function that accesses each of the properties you want to watch and then returns a new empty object. Vue knows product.p1 and product.p2 were accessed in the watch function, so it will re-execute it any time either of those properties change. Then, by returning a new empty object instance from the watch function, Vue will trigger the watch handler because the watch function returned a new value (and thus what is being watched "changed").
created() {
this.$watch(() => {
// Touch the properties we want to watch
this.product.p1;
this.product.p2;
// Return a new value so Vue calls the handler when
// this function is re-executed
return {};
}, () => {
// p1 or p2 changed
})
}
Pros:
You don't have to stringify anything.
You don't have to debounce the watch handler function.
Cons:
You can't track the previous values of p1 and p2.
Take care if this.product could ever be null/undefined.
It will always trigger when p1 or p2 are changed; even if p1 and p2 are set back to their previous values before the next micro task (i.e. $nextTick()); but this is unlikely to be a problem in most cases.
You need to use this.$watch(). If you want to use the watch option instead then you need to watch a computed property.
Some of these cons apply to other approaches anyway.
A more compact version would be:
this.$watch(
() => (this.product.p1, this.product.p2, {}),
() => {
// changed
}
})
As some of other developers said you can use computed properties to monitor the changing of product.p1 or product.p2 or both of them and then calling loadData() method only once in each case. Here is the code of a hypothetical product.vue component:
<template>
<div>
this is product compo
</div>
</template>
<script>
export default {
name: "product",
watch: {
p1p2: function(newVal, oldVal) {
this.loadData();
}
},
props: {
productProp: {
type: Object,
default: () => {},
},
},
computed: {
p1p2: function() {
return this.productProp.p1 + this.productProp.p2;
}
},
methods: {
loadData() {
console.log("load data method");
}
},
}
</script>
I renamed the prop that it received to productProp and watched for a computed property called p1p2 in that. I supposed that the values of data are in String format (but if they are not you could convert them). Actually p1p2 is the concatenation of productProp.p1 and productProp.p2. So changing one or both of them could fire the loadData() method. Here is the code of a parent component that passes data to product.vue:
<template>
<section>
<product :productProp = "dataObj"></product>
<div class="d-flex justify-space-between mt-4">
<v-btn #click="changeP1()">change p1</v-btn>
<v-btn #click="changeP2()">change p2</v-btn>
<v-btn #click="changeBoth()">change both</v-btn>
<v-btn #click="changeOthers()">change others</v-btn>
</div>
</section>
</template>
<script>
import product from "../components/product";
export default {
name: 'parentCompo',
data () {
return {
dataObj: {
p1: "name1",
p2: "name2",
p3: "name3",
p4: "name4"
}
}
},
components: {
product
},
methods: {
changeP1: function() {
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeP2: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
},
changeBoth: function() {
if (this.dataObj.p2 == "name2") {
this.dataObj.p2 = "product2"
} else {
this.dataObj.p2 = "name2"
}
if (this.dataObj.p1 == "name1") {
this.dataObj.p1 = "product1"
} else {
this.dataObj.p1 = "name1"
}
},
changeOthers: function() {
if (this.dataObj.p3 == "name3") {
this.dataObj.p3 = "product3"
} else {
this.dataObj.p3 = "name3"
}
}
},
}
</script>
You can test the change buttons to see that by changing dataObj.p1 or dataObj.p2 or both of them the loadData() method only called once and by changing others it is not called.
for you do ontouch event in vuejs while using it with your HTML inline
and you have an object model that house you other variable and you need to validate the any of the variable you will need to put them in watcher and use qoute ('') to tell vuejs that this is from a the model.email
hope this is useful
data() {
return {
model: {},
error_message: [],
}
},
watch: {
'model.EmailAddress'(value) {
// binding this to the data value in the email input
this.model.EmailAddress = value;
this.validateEmail(value);
},
},
methods: {
validateEmail(value) {
if (/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
this.error_message['EmailAddress'] = '';
} else {
this.error_message['EmailAddress'] = 'Invalid Email Address';
}
}
},

React: Cannot read property 'map' of undefined with conditional statement

guys hope everyine is doing well. So I am new to React and attempting to create a Stock APP. This component fetches the stock data for a stock I select from my Stocklist( another component) . I'm using a custom hook and it's functioning properly, but I am getting the cannot read property 'map' of undefined . The Json I am expecting has two arrays , detailed and aggregated and each array contains objects with a date property and a price property. I am attempting to map that data so that I can produce it into a graph.
The problem is the map function is executing before I select this activeStock, so it's trying to map a property of null, even though I put it in a conditional statement so it shouldn't execute
import React from 'react';
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'
import useFetch, { DEFAULT_OPTIONS } from '../shared/useFetch';
const StockGraph = (props) => {
const activeStock = props.activeStock
const string = "stocks/"
const activeURL = string.concat(activeStock, "/price/today")
console.log(activeURL)
const [stocks, isLoading, error] = useFetch(activeURL, DEFAULT_OPTIONS);
if (error) {
return <div>`Error: {error}`</div>;
} else if (isLoading) {
return <div>Loading...</div>;
} else if (stocks != null ) {
const stockdetails= stocks.detailed
const detailed = stockdetails.map((item) => {
return {
x: new Date(item.date),
y: item.price
};
});
const stocksaggr= stocks.aggregated
const aggregated = stocks.aggregated.map((item) => {
return {
x: new Date(item.date),
y: item.price
};
});
const options = {
chart: {
type: 'line'
},
title: {
text: activeStock
},
subtitle: {
text: ''
},
xAxis: { type: 'datetime' },
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: false
}
},
series: [
{
name: 'detailed',
data: detailed
}
,
{
name: 'aggregated',
data: aggregated
}
]
};
console.log('graph-render', stocks)
return (
<section className='stock-graph'>
<div id="stockGraphContainer" className="stock-graph__container">
<HighchartsReact
highcharts={Highcharts}
options={options}
/>
</div>
</section>
)
}
else {
return null;
}
}
export default StockGraph;
Sounds like stocks.detailed or stocks.aggregated itself is undefined. You need to add either add another conditional statement to check for that before executing the map. Or, maybe it shows another underlying problem with your code - should stocks.aggregated/detailed be defined at that point? If so, why isn't it? I can't tell you from what information you've given, unfortunately.
Like what others said, your conditional is only checking if stocks is null. stocks.detailed or stocks.aggregated is what is being mapped so check if those values even exist. Double check if your useFetch() hook is really working as expected and getting the values you need. Throw logs at key points and you'll get closer to pinpointing what is going on.

Keep getting [Object Object] in hybrid app Javascript

I'm building a hybrid app using Nuxt JS, Cordova and Cordova Native Storage (essentially localstorage).
I'm saving an object to native storage, and retrieving it on page load within mounted() however, I keep getting the following error no matter what I try to access the object data:
[Object Object]
My JS in the component which is loaded on every page is:
import { mapState } from 'vuex';
export default {
mounted () {
document.addEventListener("deviceready", this.getNativeStorage(), false)
},
methods: {
getNativeStorage() {
window.NativeStorage.getItem("beacon_native_storage", (value) => {
var parseObj = JSON.parse(value)
alert(parseObj)
alert(parseObj.localStorage)
}, (error) => {
alert(`Error: ${error.code}-${error.exception}`)
});
},
refreshNativeStorage(currentState) {
window.NativeStorage.initWithSuiteName("beacon");
window.NativeStorage.setItem("beacon_native_storage", JSON.stringify(currentState), () => {
alert('Stored currentState')
}, (error) => {
alert(`Error: ${error.code}`)
});
}
},
computed: {
state () {
return this.$store.state
}
},
watch: {
state: {
handler: function (val, Oldval) {
setTimeout(function () {
this.refreshNativeStorage(this.state)
}.bind(this), 10)
},
deep: true
}
}
}
And the object from Vuex looks like:
export const state = () => ({
pageTitle: 'App name',
dataUrls: [],
intervalData: [],
settings: [],
experimentalFeatures: [],
customAlertSeen: false,
user: null,
account: null,
payloadOutput: null
})
Every time the getItem runs, alert(parseObj) always returns [Object Object] rather than for instance, the data. And if I try returningparseObj.localStorage.pageTitlewhich is clearly defined instore/localStorage.jsit returnsundefined`
Where am I going wrong here?
So, what happens, is that localStorage stores STRINGS, not objects.
When you save your item to localStorage, first convert it to a string, then parse it from a string when you retrieve it.
localStorage.setItem('a', {b:'c',d:'e'})
localStorage.getItem('a') // "[object Object]" <- note the quotes!
localStorage.setItem('a', JSON.stringify({b:'c',d:'e'}))
JSON.parse(localStorage.getItem('a')) // {b: "c", d: "e"}

ESlint Error when using map - workaround?

What is the workaround to update the dataLine when using data.Items.map()
I am getting eslint error:
Assignment to property of function parameter 'dataLine'
You can see I am deleting Other property and modifying dataLine.Config
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
console.log(JSON.stringify(newItems, null, 2));
About eslint, I think it's a missing piece, because if you write your function in an equivalent way:
data.Items.map((dataLine) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
you won't receive any warning. Maybe it's the case of open an issue there.
You could pass {props : true}, like GProst said, but this will enforce you to not make the assignment of any property of the parameter, which is a good thing, for example:
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = { // not allowed with props : true
Size: "L"
};
delete dataLine.Other; // not allowed with props : true
}
return dataLine;
});
Why eslint have such a rule?
You are modifying the properties of data.Items, this will cause side effects on the external environment of the callback function on map. In some cases this will put you in bad situation, like not knowing which piece of code removed some property.
A suggestion about how you can deal with this safely is return an entire new object to make your data.Items immutable in your case:
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
const dataLineCopy = JSON.parse(JSON.stringify(dataLine))
if (data.Type == "API") {
dataLineCopy.Config = {
Size: "L"
};
delete dataLineCopy.Other;
}
return dataLineCopy;
});
console.log(JSON.stringify(newItems, null, 2));
Edit no-param-reassign rule in eslint config, set option props to false:
"no-param-reassign": ["error", { "props": false }]

Categories

Resources