Vue.js 3 access to this undefined - vue.js

I'm new to Vue.js 3, but I have a strange behavior accessing the “this” object in a component.
If my component is declared with the script setup, the access to “this” object is always undefined, see the below code:
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.info("Access KO: " + this)
})
</script>
If I use the export default, everything works fine, see the below code:
<script>
export default {
mounted() {
console.info("Access OK: " + JSON.stringify(this) + "<<")
}
}
</script>
Do you have any idea?
Thanks and regards.
Giuseppe

Arrow functions have a lexical this and its value within the arrow function is determined by the surrounding scope.In your case there is no surrounding scope.
that's the reason why we go with primitive functions.
Try the below code and see if this is working
<script setup>
import { onMounted } from 'vue';
onMounted() {
console.info("Access KO: " + this)
})
</script>
Note: There is nothing to do with export default. If you carefully observe, you are using primitive function in the second codeblock thatswhy its working.

onMounted uses callbacks, correct usage in this case should be like this
onMounted( () => console.info("Access KO: " + this))

Related

How to fire an $emit event from Vue Composable

I have a vue composable that needs to fire an event. I naively set it up as follows:
*// composable.js*
import { defineEmits } from "vue";
export default function useComposable() {
// Vars
let buffer = [];
let lastKeyTime = Date.now();
const emit = defineEmits(["updateState"]);
document.addEventListener("keydown", (e) => {
// code
emit("updateState", data);
}
// *App.vue*
<template>
<uses-composables
v-show="wirtleState.newGame"
#updateState="initVars"
></uses-composables>
</template>
<script setup>
const initVars = (data) => {
//code here
}
// usesComposable.vue
<template>
<button #click="resetBoard" class="reset-button">Play Again</button>
</template>
<script setup>
import { defineEmits } from "vue";
import useEasterEgg from "#/components/modules/wirdle_helpers/useEasterEgg.js";
useEasterEgg();
</script>
The error I get is "Uncaught TypeError: emit is not a function useEasterEgg.js:30:11
So obviously you can not use defineEmits in a .js file. I dont see anywhere in Vue docs where they specifically use this scenario. I dont see any other way to do this but using $emits but that is invoked in a template which my composable does not have. Any enlightenment much appreciated.
You can emit events from a composable, but it will need to know where the events should be fired from using context which can be accessed from the second prop passed to the setup function: https://vuejs.org/api/composition-api-setup.html#setup-context
Composable:
export default function useComposable(context) {
context.emit("some-event")
}
Component script:
<script>
import useComposable from "./useComposable"
export default {
emits: ["some-event"],
setup(props, context) {
useComposable(context)
}
}
</script>
To use it in a script setup, the best way I found was to declare the defineEmit first, and assigning it to a const, and pass it as a param to your composable :
const emit = defineEmit(['example']
useMyComposable(emit);
function useMyComposable(emit){
emit('example')
}
You can't access emit this way, as the doc says : defineProps and defineEmits are compiler macros only usable inside script setup. https://vuejs.org/api/sfc-script-setup.html
I'm not entirely sure of what you are trying to achieve but you can use vue-use composable library to listen to key strokes https://vueuse.org/core/onkeystroke/
Lx4
This question is a bit confusing. What is <uses-composable>? A composable generally is plan js/ts, with no template. If it had a template, it would just be a component. Even if it was a component, which I mean you could turn it into if thats what you wanted, I don't see anything there that suggests that would be a good idea.
So I guess the question is, why would you want to emit an event? If I'm following what you want, you can just do:
// inside useEasterEgg.js
document.addEventListener('keydown', () => {
// other code
const data = 'test';
updateStateCallback(data);
});
function useEasterEgg() {
function onUpdateState(callback) {
updateStateCallback = callback;
}
return {
onUpdateState,
}
}
// Put this wherever you want to listen to the event
const { onUpdateState } = useEasterEgg();
onUpdateState(data => console.log(data));
https://jsfiddle.net/tdfu3em1/3/
Alternatively, if you just want access to data, why not make it a ref and just use it where you want:
const data = ref();
document.addEventListener('keydown', () => {
// other code
data.value = resultOfOtherCode;
});
function useEasterEgg() {
return {
data,
}
}
// Anywhere you want to use it
const { data } = useEasterEgg();

Why is a variable declared in onBeforeMount() not known in the main section of <script>?

I have a Vue3 component in which I use a pre-mount hook (this is a trimmed-down version to scope it to the problem):
<script setup lang="ts">
const hello = () => {
let a = [... allNotes.value]
}
onBeforeMount(() => {
let allNotes = ref([])
}
</script>
With this, I get a ReferenceError: allNotes is not defined on the line that defines a.
Why is it so? Isn't allNotes known to hello() after the mount?
Why are you defining a reactive variable from inside a lifecycle hook? If you want allNotes to be available in the component or in the rest of your script, you only need to declare it in the top level of <script setup>. Remember that the Composition API setup() function replaces the beforeCreate and created lifecycle hooks, so anything defined within setup() will be available everywhere. You can read more about that in the Vue Docs
Specifically the problem here is that allNotes is scoped to the onBeforeMount() function only, and as such isn't known to the rest of your script. Once onBeforeMount() is called and finished, it will destroy allNotes and it will no longer exist.
You can just do
<script setup>
const allNotes = ref([])
const hello = () => {
allNotes.value.push("Hello")
}
</script>
To illustrate the point with the OptionsAPI, what you're doing is the same as something like this:
export default {
beforeMount() {
const allNotes = [];
},
methods: {
hello() {
this.allNotes.push("Hello!");
}
}
}
That won't work, since allNotes only exists inside of the beforeMount() hook. You'd have to declare allNotes in the data() or computed() properties in order for your hello() method to be able to use it.
Also, as a small sidenote, you should declare reactive elements with const and not let. It's a bit weird because you technically are changing its value, but the Vue internals make it so you're actually changing a copy of it that exists inside of Vue. As such, you're not actually modifying the original value, so let is inappropriate.

How to `emit` event out of `setup` method in vue3?

I know I can call the emit method from the setup method, but is there any way to emit event from any other functions without passing the emit method from setup method(not the the functions in the methods option, but a useXXX function) ?
setup function takes two arguments, First one is props.
And the second one is context which exposes three component properties, attrs, slots and emit.
You can access emit from context like:
export default {
setup(props, context) {
context.emit('event');
},
};
or
export default {
setup(props, { emit }) {
emit('event');
},
};
Source
in vue3 typescript setup
<script setup lang="ts">
const emit = defineEmits()
emit('type', 'data')
<script>
20220626
<script setup lang="ts">
const emit = defineEmits(['emit_a', 'emit_b'])
emit('emit_a')
emit('emit_b', 'emit_b_data')
<script>
With Vue 3 setup syntax sugar
<script setup lang="ts">
import { defineEmits } from 'vue'
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
function yourFunction (id: number) {
emit('change', id)
}
<script>
See docs: https://v3.vuejs.org/api/sfc-script-setup.html#typescript-only-features
Here's the proper way to emit events programmatically (using javascript) in vue3:
export default defineComponent({
// See: https://vuejs.org/guide/components/events.html#declaring-emitted-events=
emits: 'myEventName', // <--- don't forget to declare custom events emitted
setup(_, { emit }) {
emit('myEventName') // <--- emit custom event programmatically whenever we want
},
})
The emits function can just as easily be passed as a param to any function not declared inside setup.
Side-note regarding other answers: we should avoid using getCurrentInstance(), which was intended for library authors needing access to internals of vue components (a.k.a. this of vue v2), when there are better alternatives. Especially when those alternatives were designed explicitly for our use case.
methods: {
minhaFuncao(){
let data = "conteudo";
this.$emit("nomeDoMEuEvento", data);
}
}
SEE MORE AT :https://github.com/Carlos-Alexandre-Leutz/emitir-eventos-filho-pra-pai-com-dados-no-vue3
export const useEmit = () => {
const vm = getCurrentInstance()
const emitFactory = (event: string) => (...args: any[]) => vm.emit(event, ...args)
return {
emit: vm.emit,
emitModel: emitFactory('update:modelValue')
}
}
const useButtonHandlers = () => {
const { emit } = useEmit()
const onClick = () => emit('click')
return {
onClick
}
}
You can use getCurrentInstance from Vue. You can check it out in the docs.
Usage is like
function useFunctionThatEmitsSomething(){
const instance = getCurrentInstance();
// do something
instance.emit('event');
}
Edit: Even though this answer solves the author's problem, as per the linked docs, this method is intended only for ADVANCED use cases, e.g authoring a plugin or library. For common use cases, like building a simple SPA, using this is TOTALLY DISCOURAGED and should be avoided at all costs, since it can lead to unreadable and unmaintenable code. If you feel the need to use this in a case like that, you're probably doing something wrong.

Call mixin function from asyncData() method of the page component with Nuxt.js

Can I call mixin function from asyncData() method of the page component with Nuxt.js?
My code:
<template>
...
</template>
<script>
import api from "#/plugins/api/api.js"
...
export default {
...
async asyncData(context) {
...
context.apiMethodName()
...
}
...
}
...
</script>
api.js
import Vue from 'vue'
import API from '#/assets/js/api'
Vue.mixin({
methods: {
apiMethodName() { ... }
}
})
You cant call vue methods from withing asyncData, because asyncData executed before vue have an instance.
You can extract method into simple js function and call it both in asyncData and your vue method, but keep in mind that in asyncData you wont be able to access vue instance properties and other methods
You can inject mixin into app, see https://nuxtjs.org/guide/plugins#inject-in-root-amp-context
I see it is quite late for the answer, but it is possible.
template.vue
<template>
...
</template>
<script>
import api from "~/mixins/api/api"
...
export default {
...
async asyncData({context}) {
...
// You don't need to use context
// You have to use "api" like this:
const collection = await api.methods.apiMethodName()
...
// bear in mind you should return data from this function
return {
collection,
...
}
}
...
}
...
</script>
~/mixins/api/api.js
const api = {
...
methods: {
async apiMethodName() {
...
// better use here try / catch or Promise with then / catch
const data = await do_something()
...
return data
}
...
}
...
}
export api
A similar approach was tested with stack VueJS + NuxtJS and it is working on the live website https://elfinforce.com.
you can access global mixin methods like this:
app.router.app.gloablMethod()
so simple!
You need to abandon using mixins and inject your methods instead.
First; Replace your mixin method to be
export default ({ app }, inject) => {
inject('apiMethodName', () => {
return 'some data!';
})
}
Then, inside asyncData() method, call apiMethodName() function like so
async asyncData(context) {
context.app.$apiMethodName();
})

Can be exclude `this` keyword in vue.js application?

Actually I am following Douglas Crockford jslint .
It give warning when i use this.
[jslint] Unexpected 'this'. (unexpected_a) 
I can not see any solution around for the error . Don't say add this in jslist.options and mark it true.
Is there is any approach without using this?
EDIT
ADDED CODE
// some vue component here
<script>
export default {
name: "RefereshPage",
data() {
return {
progressValue: 0
}
},
methods:{
getRefreshQueue(loader){
console.log(this.progressValue); // ERROR come here [jslint] Unexpected 'this'. (unexpected_a) 
}
}
}
</script>
Check out this jsfiddle. How can you avoid using this?
https://jsfiddle.net/himmsharma99/ctj4sm7f/5/
as i already stated in the comments:
using this is an integral part of how vue.js works within a component. you can read more about how it proxies and keeps track of dependencies here: https://v2.vuejs.org/v2/api/#Options-Data
As others have said, you're better off just disabling your linter or switching to ESLint. But if you insist on a workaround, you could use a mixin and the $mount method on a vue instance to avoid using this altogether ..
let vm;
const weaselMixin = {
methods: {
getdata() {
console.log(vm.users.foo.name);
}
},
mounted: function () {
vm.getdata();
}
};
vm = new Vue({
mixins: [weaselMixin],
data: {
users: {
foo: {
name: "aa"
}
}
}
});
vm.$mount('#app');
See the modified JSFiddle
As you can see, this only complicates what should be a fairly simple component. It only goes to show that you shouldn't break the way vue works just to satisfy your linter.
I would suggest you go through this article. Particularly important is this part ..
Vue.js proxies our data and methods to be available in the this context. So by writing this.firstName, we can access the firstName property within the data object. Note that this is required.
In the code you posted, you appear to be missing a } after getRefreshQueue definition. It's not the error your linter is describing, but maybe it got confused by the syntax error?
It is possible using the new Composition API. Vue 3 will have in-built support, but you can use this package for Vue 2 support.
An example of a component without this (from vuejs.org):
<template>
<button #click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>