Variable set in fetch() becomes empty in method() NuxtJS - vue.js

I have a simple NuxtJS page.
My part of code:
<template>
<div>
<button #click="btnClick">
Click
</button>
</div>
</template>
<script>
export default {
name: 'TestPage',
data () {
return {
testArray: []
}
},
fetch () {
// eslint-disable-next-line dot-notation
this.testArray['key'] = '123321'
console.log('Log1========================')
console.log(this.testArray)
console.log('====================================')
},
methods: {
btnClick () {
console.log('Log2========================')
console.log(this.testArray)
console.log('====================================')
}
}
}
</script>
So, when I am loading this page or reload page, I have got in Console:
[Log] Log1========================
[Log] [ key: '123321' ]
[Log] ====================================
but when I click on a button, array is empty:
[Log] Log2========================
[Log] [] (0)
[Log] ====================================
I am using Nuxt 2.15.8.
I can't understand, what's problem can be, because if my memory serves me right, I have done the same things many times, and there was no problems with this.

You're actually defining testArray as an object at your line this.testArray['key'] = '123321' you can either change your definition of testArray to an object testArray: {} or add '123321' to your array with testArray.push('123321')

Related

How to properly watch an object in Vue?

I am trying to use a Vue watcher on a computed object and it is not working at all. It works properly if it's just a string, but not if it's an object. I followed the Vue documentation but I am still getting nothing logged to the console when the object changes. The properties of the object and computed property are changing, as I have confirmed in Vue Tools. What am I doing wrong? Thanks
<v-btn small dark #click="test1.asdf = 'blah'">Test</v-btn>
data() {
return {
test1: {},
}
},
computed: {
test() {
return this.test1
}
},
watch: {
test: {
handler: function(val, oldVal) {
console.log(oldVal, val);
},
deep: true
}
}
Try this code, its works fine
<template>
<div id="app">
{{ test1 }}
<hr />
<button #click="test1.asdf = 'blah'">Click</button>
</div>
</template>
<script >
export default {
data() {
return {
test1: { asdf: "HEY" },
};
},
computed: {
test() {
return this.test1;
},
},
watch: {
test: {
handler: function (val, oldVal) {
console.log(oldVal, val);
},
deep: true,
},
},
};
</script>
I'd guess in your case you should add .native at end of your click event like this #click.native="test1.asdf = 'blah'"
Just tried by replacing the whole object. It works pretty well, and there is no need to initialize your object with the asdf property in your data:
<v-btn small dark #click="test1 = { ...test1, asdf: 'blah'}">Test</v-btn>
The spread syntax ...test1 helps keeping other properties in the target object if there is any, so you can safely add the asdf property without worrying about losing anything.
JSFiddle: https://jsfiddle.net/vh72a3bs/22/
#BadCoder Objects and Arrays are pass by reference in JavaScript, not pass by value. This means that when you add a key to the object as you are you doing in your question, you're just adding to the same Object. It's contents have changed but your variable test1 is still referencing the original object and unaware that its contents have updated. The watcher doesn't pick this change up. You can add deep: true to your watcher as another answerer has suggested, but this only watches for a couple of levels deep, so not ideal if you have a large object with lots of nested data. The most reliable way to trigger a watcher when dealing with arrays or objects is to create a new instance of that object. You can achieve this with object destructing.
Something like,
<v-btn small dark #click="test1 = { ...test1, asdf: 'blah'}">Test</v-btn>
works because you're creating a new object (using the previous objects attributes, plus anything new), triggering the watcher.

Observer stop working when object changed Vue.js

When I add dynamic keys in object, the observer in inputs stops working. For example:
<template lang="pug">
input#field(v-model="block[current]")
button(#click="current = 'de'") change
button(#click="addVal") add
</template>
<script>
data() {
return {
current: "en"
block: {}
}
},
methods: {
addVal() {
this.block.de = "adawdawdawd";
}
}
</script>
Now if I type in #fiel, block.de wont work. But if I'm not adding values, it works fine.
Try to use this.$set to update it because you have a reactivity issue:
this.$set(this.block,"de" , "adawdawdawd");

Able to display the result of a promise but length of the result appears as undefined

I'm new to vue/promise and I am struggling to understand why when I try to display the result of a promise I end up with the expected data but when I try to find out its length, it says undefined
When I try to display the alerts from displayAlerts() , I can see a list of alerts, 2 in total. However in computed within the title function ${this.displayAlerts.length} appears as undefined, I was expecting to see 2.
Does it have something to do with displayAlerts() resulting in a promise? How do I fix the code such that I get 2 instead of undefined?
The code is below:
<template>
<div>
{{displayAlerts}}
<li v-for="alert in alerts" class="alert">
{{alert['name']}}
</li>
</div>
</template>
export default {
data () {
return {
alerts: null,
alert: new Alert(),
updatedAlert: new Alert(),
deletedAlert: new Alert(),
};
},
computed: {
...mapGetters("authentication",['token']),
...mapGetters("user",['profile']),
displayAlerts() {
return getUserAlert({
user_id: this.profile.user_id,
token: this.token
}).then(response => (this.alerts = response.data)).catch(
error => console.log(error)
)
},
title () {
return `My Alerts (${this.displayAlerts.length})`
},
test2() {
return [1,2,3]
},
}
};
</script>
Something like this should work:
<template>
<div v-if="alerts">
<h4>{{ title }}</h4>
<li v-for="alert in alerts" class="alert">
{{ alert.name }}
</li>
</div>
</template>
export default {
data () {
return {
alerts: null
}
},
computed: {
...mapGetters('authentication', ['token']),
...mapGetters('user', ['profile']),
title () {
// Handle the null case
const alerts = this.alerts || []
return `My Alerts (${alerts.length})`
}
},
methods: {
// This needs to be in the methods, not a computed property
displayAlerts () {
return getUserAlert({
user_id: this.profile.user_id,
token: this.token
}).then(response => (this.alerts = response.data)).catch(
error => console.log(error)
)
}
},
// Initiate loading in a hook, not via the template
created () {
this.displayAlerts()
}
}
</script>
Notes:
Computed properties shouldn't have side-effects. Anything asynchronous falls into that category. I've moved displayAlerts to a method instead.
Templates shouldn't have side-effects. The call to load the data should be in a hook such as created or mounted instead.
title needs to access this.alerts rather than trying to manipulate the promise.
While the data is loading the value of alerts will be null. You need to handle that in some way. I've included a v-if in the template and some extra handling in title. You may choose to handle it differently.
I've added title to the template but that's just for demonstration purposes. You can, of course, do whatever you want with it.
I've assumed that your original displayAlerts function was working correctly and successfully populates alerts. You may want to rename it to something more appropriate, like loadAlerts.

Get id of item clicked and use it for creating dynamic url in vuejs

I have a vue bootstrap table displaying, in each row, few properties of objects of an array (got through an api call with axios).
Every row has a button that should redirect me to a detail page, with more properties of that object, plus a map.
I was thinking to make a function to get the property id of the object contained in the clicked row, but I'm not sure on how to do it. I need the id to use it in the last part of the api call.
The store is structured so that I have a module for the user and another one for these objects (activities). In these modules I deal with state, actions and mutations. A separate file handles the getters. As these activities will be modified, I need to save their state too.
I will also need to be able to easily access all the properties of the single object (not only the ones shown in the table row) from other components.
I'm getting very confused.
Here the code:
Table with all the activities:
<b-table
responsive
:fields="fields"
:items="activity"
>
<template
slot="actions"
>
<b-button
v-b-tooltip.hover
title="Mostra dettagli"
variant="info"
class="px-3"
#click="goToActivityDetail"
>
<span class="svg-container">
<svg-icon icon-class="search"/>
</span>
</b-button>
</template>
</b-table>
In the script:
export default {
name: 'AllActivities',
data() {
return {
fields: [
{ key: 'activity.activityName', label: 'Activity', _showDetails: true},
{ key: 'related_activity', label: 'Related activity', _showDetails: true},
{ key: 'start', label: 'Start', _showDetails: true },
{ key: 'end', label: 'End', _showDetails: true },
{ key: 'travel_mode', label: 'Travel mode', _showDetails: true },
{ key: 'actions', label: '' }
],
activity: [],
methods: {
getIdActivity(){
**?? how to get it ??**
},
goToActivityDetail() {
this.$router.push({
name: 'activityDetail'
})
}
}
goToActivityDetail()
obviously does not work, in the console:
- [vue-router] missing param for named route "activityDetail": Expected "activityId" to be defined
- [vue-router] missing param for redirect route with path "/see-all-activities/:activityId": Expected "activityId" to be defined)
In the getters file I have:
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token
}
export default getters
So here I will need to have something like:
activityId: state => state.activity.activityId
Which is coming from activity.js, which is:
import {
getActivityId
} from '#/components/AllActivities'
const state = {
activityId: getActivityId()
}
const mutations = {
SET_ACTIVITY_ID: (state, activityId) => {
state.activityId = activityId
}
}
const actions = {
setActivityId({
commit
}) {
return new Promise(resolve => {
commit('SET_ACTIVITY_ID', '')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
IF this is right, what is left is the function to get the id of the object contained in the table row clicked.
Also, how to write that activity id in the api call (axios)?
Now I have:
export function getSingleActivity() {
return request({
url: 'http://localhost:8000/api/user_activity/:activityId',
method: 'get'
})
}
But I am not sure if that's correct.
Also, how to access the other properties (to be displayed in the detailActivity page)?
This will be made of a list of some properties (probably a stacked table component) and a map component, so I will need to access the properties in both these components.
I hope I've been clear enough,
thank you.
It was dead simple. I post how to solve it in case someone else get stuck on this too.
I added a slot scope to the template that contains the button:
<template
slot="actions"
slot-scope="data"
>
Then I added the single activity (following the vue bootstrap markup data.item) as parameter to the button click
#click="goToDetailActivity(data.item)"
And the function called by the click became:
goToDetailActivity(activity) {
this.$router.push({
name: 'DettaglioAttivita',
params: { activityId: activity.id }
})
}
That's it.
Worth mentioning is you're using vuex. If I understand correctly you want to get the property read from vuex?
To read a property from vuex you can eather use this.$store.getters.activity
Or use mapGetter.
Read the following page https://vuex.vuejs.org/guide/getters.html
Also you have to set the param when you do a router.push
router.push({ name: 'activity', params: { id: activityId } })

How to change the value of a prop (or data) of a component, from OUTSIDE the component?

As the title says, I'm trying to change the value of a prop/data in a component, but the trigger is being fired from outside the component, from something that has nothing to do with Vuejs.
Currently I trying to use a Simple State Manager, based on the docs from here, like so:
var store = {
debug: true,
state: {
progress: 23
},
setProgress (uff) {
if (this.debug) console.log(uff)
this.state.progress = uff
}
}
The documentation leads me to believe that if the value of progress is mutated, the value of my Vue instance would also change if I link them accordingly. But this doesn't work in a component (my guess would be it's cause it's a function).
This is part of my component:
Vue.component('transcoding', {
data () {
return {
progress: store.state.progress
}
},
template: `
<v-progress-circular
:size="130"
:width="25"
:value="progress"
color="teal"
>
{{progress}}
</v-progress-circular>
`
})
So, when I trigger a store.setProgress(value), nothing happens. The log messages do happen, but the state isn't updated in the component.
This is the script that's currently triggering the state change:
App.cable.subscriptions.create(
{ channel: "ProgressChannel", room: "2" },
{ received: function() {
store.setProgress(arguments[0])
}}
)
It's an ActionCable websocket in Ruby on Rails. The trigger works perfectly, but I just cannot make the connection between the state change and the component.
I tried loading this script in the mounted() event for the component, thinking I could reference the value like this:
Vue.component('transcoding', {
data () {
return {
progress: 0
}
},
template: `
<v-progress-circular
:size="130"
:width="25"
:value="progress"
color="teal"
>
{{progress}}
</v-progress-circular>
`,
methods: {
setProgress: function(uff) {
this.progress = uff
}
},
mounted() {
App.cable.subscriptions.create(
{ channel: "ProgressChannel", room: "2" },
{ received: function() {
this.setProgress(arguments[0])
}}
)
}
})
But this gives me an error saying that this.setProgress is not a function, which is obvious since I'm calling it within the create method of App.cable.subscriptions.
How can I make this work? I realize I'm mixing things with my question, but I wanted to illustrate what my goal is. I simply want to know how to make the component's progress data to update, either from the outside, or from the component itself if I can make it find the function.
You are initializing your data item to the value from the store:
data () {
return {
progress: store.state.progress
}
}
Changes to the store will not propagate to your data item. You could eliminate the data item and just use store.state.progress where you need it, or you could create an computed that returns its value if you want a local single-name handle for it.