How to bind this in then callback in Vuex - vue.js

I am using Vuex in my app. I dispatch an action inside the component. I get back the result in then(). I need to assign part of that result to some local variables defined in the Vue component. I am only able to do this if I bind the then() with this. Is there a way to achieve the same without using bind.
Here is my original code, I am talking about the line this.$store.dispatch:
<script>
/* eslint-disable */
import * as d3 from 'd3'
import FlightCard from './FlightCard'
export default {
name: 'AirportLayout',
components: {FlightCard},
props: ['id'],
data () {
return {
cardActivator: null
}
},
methods: {
// show flight card menu
showFlightCard: function (e) {
// debugger
let flight = this.$store.state.demo.find(a => {
return a.flightNr === e.target.id
})
this.$store.dispatch('getFlightCard', {flightNr: flight.flightNr, scheduledDate: flight.scheduledDate}).then(function (){
debugger
// here I want to do something with the response
this.cardActivator = document.getElementById(e.target.id)
console.log('Showing flight card for: ', this.cardActivator)
}.bind(this)) // Using bind, this gets the value of Vue component
}
}
}
</script>
I read the documentation for arrow functions here Arrow functions, it says an arrow function does not create its own this, the this value of the enclosing execution context is used. I assumed that would be the Vue component, and I tried it, but then this value was undefined. So I concluded the execution context must be referring to the then(), am I right? If yes, how could I make this inside then() refer to the Vue component?
Here is my code using arrow functions
<script>
/* eslint-disable */
import * as d3 from 'd3'
import FlightCard from './FlightCard'
export default {
name: 'AirportLayout',
components: {FlightCard},
props: ['id'],
data () {
return {
cardActivator: null
}
},
methods: {
// show flight card menu
showFlightCard: function (e) {
// debugger
let flight = this.$store.state.demo.find(a => {
return a.flightNr === e.target.id
})
this.$store.dispatch('getFlightCard', {flightNr: flight.flightNr, scheduledDate: flight.scheduledDate}).then(r => {
debugger
// here I want to do something with the response
this.cardActivator = document.getElementById(e.target.id)
console.log('Showing flight card for: ', this.cardActivator)
}) // here the value for this is undefined
}
}
}
</script>

Related

vue3: control property with a timed function

First of all, I am a new vuejs developer and my purpose is to get acquainted with Vue, so, not going to use any external plugins or components.
I am writing a simple alert component, which looks like this:
<Alert :show="showAlert" />
I want the show property to return back to false after 2 seconds. How can I do this from inside the component (i.e., not in the page where this component is used). I tried this:
import { computed } from 'vue';
export default {
props: ['show'],
setup(props) {
const shown = computed(() => {
if (props.show) {
setTimeout(() => {
console.log("hiding the alert...")
props.show = false
}, 2000);
}
return props.show.value
})
return { shown }
}
};
the compiler said:
14:15 error Unexpected timed function in computed function vue/no-async-in-computed-properties
16:19 error Unexpected mutation of "show" prop vue/no-mutating-props
My rational is that the delay of alert should be controlled by the alert component (which could be changed by a prop), but not forcing the caller to write some thing like:
function Alert(delay) {
showAlert = true
setTimeout(() => showAlert = false, delay)
}
There are 2 errors.
First vue/no-mutating-props, props are read only so you are not supposed to change it from within the component. It is still possible to change props from outside the component and pass down to it.
For this you should copy the value of props to your data()
data() {
return {
showAlert
}
}
You should be able to update showAlert with no problem.
The second error vue/no-async-in-computed-properties, you cannot write async function inside computed(), so the alternative is to use watch instead.

How to get updated value from vuex store in component

I want to show a progress bar in a component. The value of the progress bar should be set by the value of onUploadProgress in the post request (axios). Till so far, that works well. The state is updated with that value correctly.
Now, I am trying to access that value in the component. As the value updates while sending the request, I tried using a watch, but that didn't work.
So, the question is, how to get that updated value in a component?
What I tried:
component.vue
computed: {
uploadProgress: function () {
return this.$store.state.content.object.uploadProgressStatus;
}
}
watch: {
uploadProgress: function(newVal, oldVal) { // watch it
console.log('Value changed: ', newVal, ' | was: ', oldVal)
}
}
content.js
// actions
const actions = {
editContentBlock({ commit }, contentObject) {
commit("editor/setLoading", true, { root: true });
let id = object instanceof FormData ? contentObject.get("id") : contentObject.id;
return Api()
.patch(`/contentblocks/${id}/patch/`, contentObject, {
onUploadProgress: function (progressEvent) {
commit("setOnUploadProgress", parseInt(Math.round((progressEvent.loaded / progressEvent.total) * 100)));
},
})
.then((response) => {
commit("setContentBlock", response.data.contentblock);
return response;
})
.catch((error) => {
return Promise.reject(error);
});
},
};
// mutations
const mutations = {
setOnUploadProgress(state, uploadProgress) {
return (state.object.uploadProgressStatus = uploadProgress);
},
};
Setup:
Vue 2.x
Vuex
Axios
Mutations generally are not meant to have a return value, they are just to purely there set a state value, Only getters are expected to return a value and dispatched actions return either void or a Promise.
When you dispatch an action, a dispatch returns a promise by default and in turn an action is typically used to call an endpoint that in turn on success commits a response value via a mutation and finally use a getter to get the value or map the state directly with mapState.
If you write a getter (not often required) then mapGetters is also handy to make vuex getters available directly as a computed property.
Dispatch > action > commit > mutation > get
Most of your setup appears correct so it should be just a case of resolving some reactivity issue:
// content.js
const state = {
uploadProgress: 0
}
const actions = {
editContentBlock (context, contentObject) {
// other code
.patch(`/contentblocks/${id}/patch/`, contentObject, {
onUploadProgress: function (progressEvent) {
context.commit('SET_UPLOAD_PROGRESS',
parseInt(Math.round((progressEvent.loaded / progressEvent.total) * 100)));
},
}
// other code
}
}
const mutations = {
SET_UPLOAD_PROGRESS(state, uploadProgress) {
state.uploadProgress = uploadProgress
}
}
// component.vue
<template>
<div> {{ uploadProgress }} </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState('content', ['uploadProgress']) // <-- 3 dots are required here.
}
}
</script>

How to add emit information to a component dynamically generated using Vue.extent()?

I'm dynamically generating instances of my child component "Action.vue" using Vue.extent() this way:
let ActionClass = Vue.extend(Action)
let ActionInstance = new ActionClass({
store
})
ActionInstance.$mount()
this.$refs.actions.appendChild(ActionInstance.$el)
This works fine. However, besides access to the store, child component also needs to emit an event (in response to user interaction with one of its elements) for the parent component to execute a method.
How to achieve this?
You can use instance.$on method to add eventListenersdynamically :
Consumer
import Dummy from "./Dummy.vue";
import Vue from "vue";
export default {
name: "HelloWorld",
props: {
msg: String
},
methods: {
mount: function() {
const DummyClass = Vue.extend(Dummy);
const store = { data: "some data" };
const instance = new DummyClass({ store });
instance.$mount();
instance.$on("dummyEvent", e => console.log("dummy get fired", e));
this.$refs.actions.appendChild(instance.$el);
}
}
};
Child component
export default {
methods: {
fire: function() {
console.log("fired");
this.$emit("dummyEvent", { data: "dummyData" });
}
}
};
Here is the Sandbox
https://v2.vuejs.org/v2/guide/components-custom-events.html
https://v2.vuejs.org/v2/api/#Options-Lifecycle-Hooks
You can use a lifecylce hook (for example: mounted) to emit the event when the child has been created.
you can listen to the events as documented in the documentation.
the store can be reached through this.$store.

Open modal dialog on event bus event

I've created a backend and am now trying to build a frontend, using it. I'm very new to Vue.js and am having a hard time telling it to do what I want; probably because of missing some basic concepts. Hopefully someone can point me in the right direction.
The App.vue groups following components: Header, main section (routed), footer and a modal login dialog.
The issue I'm trying to solve is to display the modal login dialog when clicking the Login button (which lives in the header component); currently, nothing besides the messages being logged happens.
For this I've created an event bus and am firing an event:
export default {
name: 'SppdTeamTunerHeader',
methods: {
emitShowLoginDialogEvent () {
EventBus.$emit('ShowLoginDialog', true)
}
}
}
Emitting the event works as I can see in the Vue DevTools for Chrome.
Here's the complete code of App.vue:
<template>
<div id="app">
<SppdTeamTunerHeader/>
<router-view></router-view>
<SppdTeamTunerFooter/>
<LoginDialogModal
v-show="isLoginDialogVisible"
/>
</div>
</template>
<script>
import SppdTeamTunerHeader from '#/components/TheHeader'
import SppdTeamTunerFooter from '#/components/TheFooter'
import LoginDialogModal from '#/components/LoginDialogModal'
import { EventBus } from '#/common/EventBus'
export default {
name: 'App',
components: {
SppdTeamTunerHeader,
SppdTeamTunerFooter,
LoginDialogModal
},
data: function () {
return {
isLoginDialogVisible: false
}
},
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + this.isLoginDialogVisible)
if (isVisible) {
this.isLoginDialogVisible = true
} else {
this.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + this.isLoginDialogVisible)
})
},
destroyed () {
EventBus.$off('ShowLoginDialog')
}
}
</script>
When checking the console, following is being printed when clicking the login button:
Setting ShowLoginDialog isVisible=true. isLoginDialogVisible=undefined
Finished setting isLoginDialogVisible=true
The value logged for isLoginDialogVisible can't come from the variable defined in the data function as it prints undefined, whereas it has been defined as false (I guess that's my main problem).
I've read quite a few articles about the subject, e.g:
https://codingexplained.com/coding/front-end/vue-js/why-components-data-properties-must-be-functions
https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
The modal dialog example I've based the implementation comes from here: https://alligator.io/vuejs/vue-modal-component/
This is happening because you are not using an Arrow function. Instead of a plain function, use arrow function like this:
mounted () {
// Note the use of arrow function.
EventBus.$on('ShowLoginDialog', (isVisible) => {
// .. All your code
})
}
If you use plain function function () {}, then this pointer is not accessible within inner function. Arrow function will lexically bind this pointer to mounted() function's this context. So use an arrow function i.e. () => {};
Note: If you insist on using plain old function syntax then use closure variable to keep track of this pointer:
mounted () {
// Assign this pointer to some closure variable
const vm = this;
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + vm.isLoginDialogVisible)
if (isVisible) {
vm.isLoginDialogVisible = true
} else {
vm.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + vm.isLoginDialogVisible)
})
}
This has nothing to do with Vue.js. It is a typical JavaScript behavior.
I believe your listener for the EventBus events needs to be accessible to App. Right now EventBus and App are two separate instances. You could mount the event handler inside App like this:
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
...
});

Use tableTop.js to return an array that can be used in Vue Components

I am attempting to build an array of objects from a spreadsheet using tableTop.js that can be passed into other functions and vue components. I have been unsuccessful in returning anything I can actually use. I found this post that got me close to what I am after however what it is returning is an array of arrays of objects with two undefined array items beginning with [ob: Observer]
If I log out data in the getLibrary() function I can see the correct array how I need to receive it in my component.
If I don't push the data into the gData array in libraryData I receive undefined in vue from the function. I have attempted promises, normal functions etc. but nothing seems to work. Very appreciative of any help anyone can provide thanks.
Image 1 is what I am logging out in library data that I am trying to receive in vue.
Image 2 is what I am getting in vue
libraryData.js
// let gData = []
export default async function () {
let spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { return getLibraryData(data,m tabletop) },
simpleSheet: true
})
}
export function getLibraryData(data, tabletop) {
// gData.push(data);
///gData = data
log(data)
// I just want to return the data here to be used in vue
return data;
}
index.js
import Vue from 'vue'
import libraryData from './partials/libraryData.js'
// Too be added into a vue-lodaer?
new Vue({
el: '#vhsLibrary',
router,
template: '<vhsLibrary/>',
})
window.addEventListener('DOMContentLoaded', () => {
libraryData()
})
vue_component.vue
<script>
import { getLibraryData } from '../../js/partials/library_data';
export default {
data: () => {
return {
gData: null
}
},
mounted () {
this.gData = getLibraryData()
log('Get Library', getLibraryData())
}
}
</script>
There's a few issues here:
You use async, but you never await. In your case, we want to await the resolution or rejection of a Promise:
export default async function () {
return await new Promise((resolve, reject) => {
const spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { resolve({data, tabletop}) },
simpleSheet: true
})
})
}
There's no reason for the additional function because it has no gains. Let's look at Vue now.
First, your gData variable is initialized as null as opposed to []. Let's change that:
data () {
return {
gData: []
}
},
Next, let's update our mounted method. We can use the same async/await pattern here:
async mounted () {
const { data } = await getLibraryData()
this.gData = data
}
And now you can v-for="(row, index) in gData" to iterate it.
Here's a codepen for you, too