What is the purpose of this.$nextTick? - javascript

I'm new to Vue js and I'm trying to understand the logic of the usage of nextTick in a method. So I have two components like this:
IsTuruIslem.vue
<template>
...
<t-row>
<is-turu-islem-goruntule ref="isTuruIslemGoruntule"
#kaydet="kaydet"
#guncelle="guncelle">
</is-turu-islem-goruntule>
</t-row>
...
</template>
<script>
...
isTuruIslemPopupAc(detay) {
if (!detay) this.$refs.isTuruIslemGoruntule.open();
else {
let guncellenecekDetay = JSON.parse(JSON.stringify(detay));
console.log("Popup:", guncellenecekDetay);
this.$refs.isTuruIslemGoruntule.open({
isUpdate: true,
detay: guncellenecekDetay
});
}
}
...
</script>
IsTuruIslemGoruntule.vue
<template>
...
<t-row>
<t-col :span="20">
<t-select
id="sirket"
ref="sirket"
label="Şirket *"
v-model="detay.sirket"
item-value="id"
item-text="aciklama"
/>
</t-col>
</t-row>
<t-row>
<t-col :span="20">
<t-select
id="cmb_durum"
ref="durum"
label="Durum"
itemText="text"
itemValue="id"
v-model="detay.durumBaseDTO"
:readonly="false"
:clearable="true"
:disabled="false"
/>
</t-col>
....
</template>
<script>
...
methods: {
open: function(options) {
this.isOpen = true;
if (options) {
this.isUpdate = true;
this.detay = JSON.parse(JSON.stringify(options.detay));
} else {
this.detay = {};
}
//this.$nextTick(() => {
this.$refs.durum
.get("/ytts/api/common/durumlar/aktifPasif", null)
.then(data => {})
.catch(err => null);
this.$refs.islem
.get("/ytts/api/tanimYonetimi/isTuruIslem/sorgula/islemListesi")
.then(data => {})
.catch(err => null);
this.$refs.sirket
.get("/ytts/api/tanimYonetimi/isTuruIslem/sorgula/sirketListesi")
.then(data => {})
.catch(err => null);
//});
//console.log("DETAY:", this.detay);
},
...
</script>
My question is code as in this example doesn't work properly and I get "Cannot read property 'get' of undefined" at the line where this.$refs.durum exists. But when I uncomment the nextTick method which resides at the top of this.$refs.durum, it magically works. I really don't get the idea of this usage. Can someone clearly explain it to me? Thank you for your attention.

If the <t-select ref="durum"> component is not created (by using v-if, for example) then this.$refs.durum won't exist.
Let's say you use something like v-if="show" with show set to false to control the creation of that component. If, in your method, you set show to true, then the component won't be created as soon as you do so, you have to wait until Vue has updated the DOM (this is a performance thing). For this, you need to use $nextTick to wait until that time, only then will the component be created and this.$refs.durum will exist.
You haven't provided all of your code, so I can't say for sure, but it looks like maybe isOpen is controlling the visibility of that component.

Related

Alpine.js: How do I access x-data from a function in an external file?

I'm just starting out with Alpine.js, I understand the basics but I'm having trouble applying them when moving functions outside of inline script tags.
For example, in index.html:
<div x-data="{ loading: false }"/>
<button
onClick="post()"
:class="{ 'active': loading === true }"
>
Post Comment
</button>
</div>
if post() was in main.ts:
const postComment = () => {
this.loading = true;
};
window.postComment = postComment;
How do I make it so that this isn't undefined?
I've found plenty of examples where functions are kept within index.html, but none where they're in a separate file.
You'll need to add the method to the AlpineJs instance to access the same scope. But you can do this with some object destructuring using the spread ... operator:
In the page:
<div x-data="{
isLoading: false,
...utils
}">
// Your content
</div>
Then in your external script file:
const utils = {
post(){
this.isLoading = true
}
}
window.utils = utils
The nice thing about this is you could put everything you need for a loading indicator in this external object to use as a mixin wherever you need it.
Here's a working example: https://codepen.io/stephenoldham/pen/BapvyYr
Update for AlpineJs v3:
If you're trying to achieve the same goal in the latest version of AlpineJs you'll need to use the Data Directive:
In the page:
<div x-data="utils">
// Your content
</div>
Then in your external script file:
document.addEventListener('alpine:init', () => {
Alpine.data('utils', () => ({
isLoading: false,
post(){
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 3000)
}
}))
})
Here's a working example:
https://codepen.io/stephenoldham-the-vuer/pen/dyJEjRx?editors=1100
Further reading in the docs covers how to set initial values and more:
https://alpinejs.dev/globals/alpine-data

Data is vanished in next line

Can anyone please explain me what is happened in these codes and how can I solve it?
I get the data in the parent's mounted function and update its data. So I have the new object in the child. But the value of the property of this object is empty!
Parent:
<template>
<div class="main-page">
<main-content v-bind:config="mainContentConfig" />
</div>
</template>
mounted(){
fetchData().then(editions => { editions.sort((e1, e2) => e1.name.toLowerCase().localeCompare(e2.name.toLowerCase()))
this.mainContentConfig.intranetEditions = [...editions];
this.mainContentConfig.currentMenuIndex = 1;
});
}
Child:
mounted(){
console.log("AA==============>", this.config);
console.log("BB==============>", this.config.intranetEditions);
}
But on the console I have:
I found this problem when I fill other data in the child class with this.config.intranetEditions array which always is empty!
Edit:
I tried this code too, but no difference!
[...this.config.intranetEditions]
Edit 2 This code tested too, but nothing!
console.log("AA==============>", this.config);
console.log("BB==============>", JSON.stringify(this.config.intranetEditions));
The child-component is mounted but the parent fetch is not finished yet, so this.config is an observer until the fetch is done (so the then is fired) and the var fulfilled.
Can you try to watch the prop config in the child-component? then you will see when this.config is fulfilled.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
UPDATE WITH EXAMPLE:
child-component
watch: {
config(newValue) {
console.log("AA==============>", newValue.intranetEditions);
checkConfigValue();
},
},
methods: {
checkConfigValue() {
console.log("BB==============>", this.config.intranetEditions);
};
},
So you can wether do something in the watcher with the newValue, or trigger a method and use this.config. Both consoles, will print the same in this case.

How to reload localStorage VueJS after using this.$router.go(-1);

This is my Login.vue:
mounted() {
if (localStorage.login) this.$router.go(-1);
},
methods: {
axios.post(ApiUrl + "/login") {
...
}
then(response => {
...
localStorage.login = true;
this.$router.go(0); /* Reload local storage */
})
}
App.vue:
mounted() {
axios
.get("/user")
.then(response => {
localStorage.user_id = response.data.user.id;
localStorage.package_id = response.data.user.package_id;
})
},
Project.vue:
mounted() {
this.user_id = localStorage.user_id
this.package_id = localStorage.package_id
}
With that above code, I cannot get localStorage.user_id and localStorage.package_id as I expected. But if I change like the follow, it worked.
mounted() {
const self = this
setTimeout(function () {
self.user_id = localStorage.user_id
self.package_id = localStorage.package_id
self.getProject();
},1000)
}
But I think setTimeout not good in that case. Is there any way to refactor this code?
Thank you!
Try this: in your root component (it's usually const app = new Vue({ ... })) write the following:
import {localStorage} from 'localStorage'; // import your module if necessary
// this is relative to the way you manage your dependencies.
const app = new Vue({
//...
data: function() {
return {
localStorage: localStorage;
}
}
})
Now whenever you want to use localStorage, access it from root component like this:
this.$root.localStorage
Hope this solves your problem.
Don't know your project structure ,but I guess it's probably an async issue. You got the user information async, so when Project.vue mounted, the request is not complete yet. As a result, the localstorage is empty at the monent.
There are two solutions for this:
Make sure Project.vue is not rendered before userinfo is complete. For example, things like <project v-if="userinfo.user_id" /> should works.
Use some data binding libary like vuex to bind userinfo to Project.vue instead of assign it in lifecycle like mounted or created.
Hope it helps.

How do I inform the parent component that something has happened in a Vue dynamic component?

I have a Vue component that generates a dynamic component, and within that dynamic component is a click handler for a button that makes an Ajax call. Upon the Ajax call being successfully completed, I want to inform the component that generates the dynamic component that the Ajax call has finished. How do I do that?
The basic structure of the code in question is as follows:
<template>
<div>
<!-- Other markup here. -->
<div class="contentPlaceholder">
</div>
</div>
</template>
<script>
export default {
// Props, etc.
data: function () {
return {
// ...
content: 'long-html-string-that-contains-Vue-components'
};
},
mounted: function () {
Vue.component(`content-component`, {
template: `
<div class="content">
${this.content}
</div>
`,
data: function () {
return {
// Local data here.
};
}
methods: {
// Button in this.content markup clicked.
btnCicked: function () {
ajax.post('url', {
// Params
success: () => {
// Want to report back to the parent component
// that we're here now.
}
});
}
}
});
const res = Vue.compile(`
<content-component>
</content-component>
`);
new Vue({
render: res.render,
staticRenderFns: res.staticRenderFns
}).$mount(`.contentPlaceholder`);
}
}
</script>
My initial thought was to do this.$emit('btnClicked', 'data-here') in the Ajax success callback, but when I try to attach an #btnClicked event handler to the content-component in either the template part of the Vue.component method call or the Vue.compile method call, I get a Vue error.
Basically, I have no clue what to do. The this context is definitely different in the dynamic component, so I can't just add a data property to the parent component and then set it in the Ajax callback of the dynamic component. I tried that and it doesn't work.
I trust there is a simple way to do this, but I'm honestly not sure how. Any help would be greatly appreciated. Thank you.
Edit: It's worth noting that I tried to treat the dynamic component as if it were just a regular child component of the parent component. As such, I added a this.$emit('btnClicked') call within the Ajax success callback and then added an #btnClicked handler to the content-component, but it didn't work.
Maybe I'm just doing it wrong, but I tried both of the following:
template: `
<div class="content" #btnClicked="btnClicked">
${this.content}
</div>
`,
// And
const res = Vue.compile(`
<content-component #btnClicked="btnClicked">
</content-component>
`);
But neither seem to work. Thanks.
btnCicked: () => { console.log(this) }.
Try to use arrow function to save the context.
Another option is to create a function that already has access to outer this, and invoke it in your method.
const method = () => {
console.log('I have access to outer this', this)
}
...
btnCicked: function () { method(); console.log('Local this', this) }
...

React async function is not a function

In one of my components I have the following functions:
addNewIndicator(attrs = {}) {
const value = attrs.value || 'Indicator'
const type = attrs.type || 'Generic Type'
this.createIndicator(value).then(
console.log('Indicator Created.')
)
}
async createIndicator(value) {
await this.props.createIndicatorMutation({
variables: {
value
},
update: (store, { data: { indicator }} ) => {
const data = store.readQuery({ query: INDICATOR_FEED_QUERY })
data.indicatorFeed.splice(0, 0, indicator)
store.writeQuery({
query: INDICATOR_FEED_QUERY,
data,
})
}
})
}
addNewIndicator() is triggered on a button click. When it runs, I get the following error:
TypeError: this.createIndicator is not a function
It is pointing to this line:
this.createIndicator(value).then(
I've done quite a bit of Googling, but haven't been able to figure out why this is the case. My understanding is that async functions can be called like that, but perhaps I'm missing something. Sorry if this is a silly question, I'm still learning React!
Also, I created the project using create-react-app and haven't modified it much other than adding some packages. Thanks for any help!
Edit to add how it is called. It is called from a child component props:
<Button primary onClick={this.handleAddSelectionClick}>Add Selected As Indicator</Button>
and handleAddSelectionClick:
handleAddSelectionClick = () => {
...snip...
this.props.addNewIndicator({
value: new_indicator_str,
})
}
try to validate that your 2 methods using the same context (this) -
you might need to do something like that in the constructor:
this.addNewIndicator = this.addNewIndicator.bind(this);
this.createIndicator = this.createIndicator.bind(this);
You probably just forgot to bind “this”. As you passed addNewIndicator as a callback, it lost its context. In this article several methods of binding callbacks are described, with all pros and cons of each.
https://reactjs.org/docs/handling-events.html

Categories