data() variable refuses to update when set inside nested functions - javascript

I am very new to VUE so please forgive if my terminology is not correct.
I am trying to set a variable which is defined in the script tag "data()" function.
I am trying to set a new value for a variable defined in data() from inside the "created()" lifecycle event. This works fine if done at the root level, but i need to do it within 2 nested calls as shown below and it will not work when nested inside the function calls:
app.vue
<template>
<div class="container">
<Button #btn-click="providerPicked" id="current-provider" :text="'Current Provider: ' + currentProvider" />
</div>
</template>
<script>
import { ZOHO } from "./assets/ZohoEmbededAppSDK.min.js";
import Button from './components/Button'
export default {
name: 'App',
components: {
Button,
},
data() {
return{
currentProvider: 'x'
}
},
created() {
console.log("CREATED HOOK")
ZOHO.embeddedApp.on("PageLoad",function(data)
{
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({Entity:entity,RecordID:recordID})
.then(function(data){
console.log(data.data[0])
console.log(data.data[0].name)
//THIS DOES NOT WORK - variable still comes back 'x' in template, notice this is nested twice.
this.currentProvider = data.data[0].name;
});
});
ZOHO.embeddedApp.init();
//THIS DOES WORK - SETS VAR TO "reee" in template, notice this is not nested
this.currentProvider = "reee"
}
}
</script>

Use arrow functions instead of anonymous functions.
Arrow functions do not bind this, and thus this will refer to the outer scope.
created() {
console.log("CREATED HOOK")
ZOHO.embeddedApp.on("PageLoad", (data) => {
console.log(data);
//Custom Business logic goes here
let entity = data.Entity;
let recordID = data.EntityId[0];
ZOHO.CRM.API.getRecord({ Entity: entity, RecordID: recordID })
.then((data) => {
console.log(data.data[0])
console.log(data.data[0].name)
//THIS DOES NOT WORK - variable still comes back 'x' in template, notice this is nested twice.
this.currentProvider = data.data[0].name;
});
});
ZOHO.embeddedApp.init();
//THIS DOES WORK - SETS VAR TO "reee" in template, notice this is not nested
this.currentProvider = "reee"
}

Related

Wait for data in mapstate to finish loading

I have stored a userProfile in Vuex to be able to access it in my whole project. But if I want to use it in the created() hook, the profile is not loaded yet. The object exists, but has no data stored in it. At least at the initial load of the page. If I access it later (eg by clicking on a button) everything works perfectly.
Is there a way to wait for the data to be finished loading?
Here is how userProfile is set in Vuex:
mutations: {
setUserProfile(state, val){
state.userProfile = val
}
},
actions: {
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.teachersCollection.doc(user.uid).get()
// set user profile in state
commit('setUserProfile', userProfile.data())
},
}
Here is the code where I want to acess it:
<template>
<div>
<h1>Test</h1>
{{userProfile.firstname}}
{{institute}}
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
data() {
return {
institute: "",
}
},
computed: {
...mapState(['userProfile']),
},
created(){
this.getInstitute();
},
methods: {
async getInstitute() {
console.log(this.userProfile); //is here still empty at initial page load
const institueDoc = await this.userProfile.institute.get();
if (institueDoc.exists) {
this.institute = institueDoc.name;
} else {
console.log('dosnt exists')
}
}
}
}
</script>
Through logging in the console, I found out that the problem is the order in which the code is run. First, the method getInstitute is run, then the action and then the mutation.
I have tried to add a loaded parameter and played arround with await to fix this issue, but nothing has worked.
Even if you make created or mounted async, they won't delay your component from rendering. They will only delay the execution of the code placed after await.
If you don't want to render a portion (or all) of your template until userProfile has an id (or any other property your users have), simply use v-if
<template v-if="userProfile.id">
<!-- your normal html here... -->
</template>
<template v-else>
loading user profile...
</template>
To execute code when userProfile changes, you could place a watcher on one of its inner properties. In your case, this should work:
export default {
data: () => ({
institute: ''
}),
computed: {
...mapState(['userProfile']),
},
watch: {
'userProfile.institute': {
async handler(institute) {
if (institute) {
const { name } = await institute.get();
if (name) {
this.institute = name;
}
}
},
immediate: true
}
}
}
Side note: Vue 3 comes with a built-in solution for this pattern, called Suspense. Unfortunately, it's only mentioned in a few places, it's not (yet) properly documented and there's a sign on it the API is likely to change.
But it's quite awesome, as the rendering condition can be completely decoupled from parent. It can be contained in the suspensible child. The only thing the child declares is: "I'm currently loading" or "I'm done loading". When all suspensibles are ready, the template default is rendered.
Also, if the children are dynamically generated and new ones are pushed, the parent suspense switches back to fallback (loading) template until the newly added children are loaded. This is done out of the box, all you need to do is declare mounted async in children.
In short, what you were expecting from Vue 2.

How to update Vue component when promise resolve AND passing props to nested children

I have parent and child components. Parent's data is populated after an ajax call, that I want to pass to child as a props.
I tried different solutions, I will post two, but not yet working : could you please pin-point the error?
The component does react to changes. I also need to ensure that props are correctly passed to child and its nested children.
Attempts:
If I don't use 'v-if' directive, I will get an error for :query is
passed through as null.
So I use v-if (see version below)
and update the rendering, but it does not react and stay empty (even
though data is properly updated).
I also tried to initialize the query data as computed without using
the v-if directive, because after all I just need to cache the result
of the premise.
I also tried to emit an event on a bus, but the component does not
react.
Finally I tried to make the component reactive by making use of a
:key (e.g. :key="ready") that should make the component reactive when
the :key changes. But still no effect.
Template:
<template>
<div v-if="isLoaded()">
<input>
<metroline></metroline>
<grid :query="query"></grid>
</div>
</template>
script:
export default {
data() {
return {
ready : false,
query : null
}
},
components : {
metroline : Metroline,
grid : Grid
},
methods: {
isLoaded() {
return this.ready
},
firstQuery( uid, limit, offset) {
var url = // my url
// using Jquery to handle cross origin issues, solving with jsonp callback...
return $.ajax({
url : url,
dataType: 'jsonp',
jsonpCallback: 'callback',
crossDomain: true
})
}
},
created() {
var vm = self;
this.firstQuery(...)
.then(function(data){
this.query = data;
console.log('firstQuery', this.query);
// attempts to pass through the query
// bus.$emit('add-query', this.query);
this.ready = true;
self.isLoaded(); // using a self variable, temporarily, jquery deferred messed up with this; however the this.query of vue instance is properly updated
})
}
}
In created hook assign the component instance this to a a variable called that and access it inside the callback because in the callback you're outside the vue component context (this):
created() {
var vm = self;
var that=this;
this.firstQuery(...)
.then(function(data){
that.query = data;
console.log('firstQuery', this.query);
that.ready = true;
self.isLoaded();
})
}
}

VueJS emit to a function outside VueJS

I'm trying to emit something from within my VueJS component to a function which sits in the html page containing the component. Am I missing something, or is this not possible?
Within my component as a method:
insert: function(){
this.$emit('insertItem', 123);
}
In the page containing the component:
<medialibrary #insertItem="insertItem(arg);"></medialibrary>
<script>
function insertItem(arg){
console.log('insertItem');
console.log(arg);
}
</script>
This is actually more possible than it seems at first look. If the function is global (in particular, visible to the parent Vue), it can be called by the Vue even if it is not a method of the Vue. (It would arguably be a better idea to create a method that calls the global function.)
The main difficulty with your code was camelCasing where it should be kebab-case.
If you want insertItem to get the arg from the $emit, the HTML should only give the function name, and Vue will take care of passing the args:
<medialibrary id="app" #insert-item="insertItem"></medialibrary>
My snippet uses your original code, which provides arg from the parent Vue.
function insertItem(arg) {
console.log('insertItem');
console.log(arg);
}
new Vue({
el: '#app',
data: {
arg: 'hi there'
},
components: {
medialibrary: {
template: '<div><button #click="insert">Insert</button></div>',
methods: {
insert() {
console.log("Emit");
this.$emit('insert-item', 123);
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.2/vue.min.js"></script>
<medialibrary id="app" #insert-item="insertItem(arg);"></medialibrary>

Vue JS 2 Data Object Changes However Method Doesn't Run

I have the following Vue JS component that I am having an issue re-rendering on data changes.
Vue.component('careers-list', {
template: `
<div id="career-list">
<div class="context">
<div v-for="career in careerData">
<h1>{{ career.fields.jobTitle }}</h1>
<div v-html="career.fields.jobDescription">
</div>
</div>
</div>
<div id="career-pagination">
<button>prev</button>
<button>1</button>
<button v-on:click="increaseCount">next</button>
</div>
</div>`,
data: function() {
return {
careerData: [],
paginationCount: 0
}
},
created: function() {
this.fetchData();
},
methods: {
fetchData: function() {
client.getEntries({
skip: this.paginationCount,
limit: this.paginationCount + 7,
order: 'sys.createdAt'
})
.then(entries => {
this.careerData = entries.items;
this.careerData.map(function (career) {
career.fields.jobDescription = (marked(career.fields.jobDescription));
});
});
},
increaseCount: function() {
this.paginationCount += 7;
}
}
});
var app = new Vue({
el: '#app'
});
So as you can see I have a fetchData method that fetches my data and formats it. Take a look at these lines within this method:
skip: this.paginationCount,
limit: this.paginationCount + 7,
Within my app these lines determine how many records on database will be returned. I am using a count for this within my data object paginationCount: 0.
I have then set an event on one of my buttons within my component template: <button v-on:click="increaseCount">next</button>
When this is clicked it updates the paginationCount using the increaseCount method.
When I render my application and click the button the paginationCount does increase however my fetchData method does not re-render the application to show the appropriate data based on the count.
I thought that Vue automatically updates any methods that have a changed data object in them however I am guessing this isn't the case or I am doing this wrong.
Any idea how I can update my fetchData method when the paginationCount is changed?
Thanks, Nick
You need to use watchers here, which suits exactly your use-case:
This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
Method is not executed as you are not returning anything from it, which depends on the vue data variable.
You have to invoke your method when paginationCount variable changes:
data: {
careerData: [],
paginationCount: 0
},
watch: {
// whenever paginationCount changes, this function will run
paginationCount: function (newPaginationCount) {
this.fetchData();
}
},

Polymer dom-repeat issue

While rendering with Polymer an array of objects, it keeps launching me an exception.
Here's the data model retrieved from server:
{
"lastUpdate":"yyyy-MM-ddTHH:mm:ss.mmm",
"info": [
{
"title": "Some nice title"
},
...
]
}
Here's my Polymer component template:
<dom-module is="items-list">
<template>
<dl>
<dt>last update:</dt>
<dd>[[$response.lastUpdate]]</dd>
<dt>total items:</dt>
<dd>[[$response.info.length]]</dd>
</dl>
<template is="dom-repeat" items="{{$response.info}}">
{{index}}: {{item.title}}
</template>
</template>
<script src="controller.js"></script>
</dom-module>
And here's the controller:
'use strict';
Polymer(
{
properties: {
info: {
type: Array
},
$response: {
type: Object,
observer: '_gotResponse'
}
},
_gotResponse: function(response)
{
console.log(response);
if (response.info.length)
{
try
{
//here I try to set info value
}
catch(e)
{
console.error(e);
}
}
},
ready: function()
{
//set some default value for info
},
attached: function()
{
//here I request the service for the info
}
}
);
If tried to set info value as:
this.info = response.info;
this.set('info', response.info);
this.push('info', response.info[i]); //inside a loop
But the result breaks after rendering the first item, the exception launched is:
"Uncaught TypeError: Cannot read property 'value' of null"
If info === $response.info then why not just use items={{info}} in the repeat?
edit:
Try to modify your code in the following way.
'use strict';
Polymer({
properties: {
$response: {
type: Object
}
},
_gotResponse: function(response)
{
console.log(response);
...
this.$response = response
...
},
attached: function()
{
//here I request the service for the info
someAjaxFn(someParam, res => this._gotResponse(res));
}
});
You don't need an observer in this case. You only use an explicit observer when the implicit ones don't/won't work. I.e. fullName:{type:String, observer:_getFirstLastName}
or
value:{type:String, observer:_storeOldVar}
...
_storeOldVar(newValue, oldValue) {
this.oldValue = oldValue;
}
if you are updating the entire array, ie this.info, then you simple use this.info = whatever once within your function. If you don't want to update the entire array, just some element within it, then you will want to use polymer's native array mutation methods as JS array method doesn't trigger the observer.
Again, since your template doesn't use info, then you don't need the property info. If you want to keep info, then don't store info within $response. In fact, $ has special meaning in polymer so try not to name properties with it. you can simply use the property info and lastUpdate for your polymer.
Last note, beware of variable scoping when you invoke functions from polymer. Since functions within polymer instances often use this to refer to itself, it may cause
Uncaught TypeError: Cannot read property 'value' ..."
as this no longer refers to the polymer instance at the time of resolve.
For example, suppose your host html is
...
<items-list id='iList'></items-list>
<script>
iList._gotResponse(json); // this will work.
setTimeout(() => iList.gotResponse(json),0); //this will also work.
function wrap (fn) {fn(json)}
wrap (iList._gotResponse); //TypeError: Cannot read property '$response' of undefined.
function wrap2(that, fn) {fn.bind(that)(json)}
wrap2 (iList,iList._gotResponse); // OK!
wrap (p=>iList._gotResponse(p)) //OK too!!
wrap (function (p) {iList._gotResponse(p)}) //OK, I think you got the idea.
</script>

Categories

Resources