How to `emit` event out of `setup` method in vue3? - vue.js

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.

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();

Difference between watch and $watch

Just a simple question.
What is the difference between options and instance methods?
Based on the watch example, we can implement watcher as an option (https://v3.vuejs.org/api/options-data.html#watch) and a method of an instance (https://v3.vuejs.org/api/instance-methods.html#watch).
From my point of understanding, I can implement exactly the same feature with both methods and the only differences would be the syntax and the place of implementation.
If I am mistaken, can somebody explain to me based on example the difference between these two?
You are indeed (almost) correct with your assumption.
There is 2 major advantage of this.$watch() though.
You can start watching dynamically
the return-value of this.$watch() is an unwatch function with which you can dynamically stop the watcher during runtime
But that doesn't necessarly mean that you should always use this.$watch() over watch: {}. The opposite. You should always think about what your use case needs
Unwatch-example:
export default {
//..
created(props) {
const unwatchAge = this.$watch(() => this.user.age, (value, oldValue) => {
if (value >= 18) {
alert('You are now allowed to drive a car!');
unwatchAge(); //we don't need age watching for anything else
}
});
}
//...
}
BTW with VUE3 you might wanna look into the watch() / watchEffect() composition API methods.
watch() does the same as watch: {} and this.$watch() and also has an unwatch-method as return-value.
watchEffect() checks any value mentioned inside parameter (function) and puts a watcher on it internally.
watch() Example (composition)
import { toRef, watch} from 'vue';
export default {
//...
setup(props) {
const age = toRef(props.age);
const unwatchAge = watch(age, console.log);
// no () => age or () => age.value needed as age is a reference by using toRef and references can be handles like this
setTimeout(() => {
console.warn('unwatching age!');
unwatchAge();
}, 5000);
}
//...
}
watchEffect() Example (composition)
import { toRef, watchEffect} from 'vue';
export default {
//...
setup(props) {
const age = toRef(props.age);
watchEffect(() => {
if (age.value >= 18) {
alert('You are now allowed to drive a car!');
}
});
//vue will internally recognize that age has to be watched here. No telling it manually.
}
//...
}
The main difference from the docs it is that the instance method returns a unwatchable that you can trigger to stop watching a certain property:
const unwatchUsers = this.$watch('users', () => {});
setTimeout(unwatchUsers, 1000);
This is not possible with options API. It is extremely useful to use this unwatch returned by this.$watch when something happens in your app.
Have in mind what is the most appropriate to your use case and use it accordingly

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.

Decentralizing functions in vuejs

Am from Angular2 whereby i was used to services and injection of services hence reusing functions how do i achieve the same in vuejs
eg:
I would like to create only one function to set and retrieve localstorage data.
so am doing it this way:
In my Login Component
this.$axios.post('login')
.then((res)=>{
localstorage.setItem('access-token', res.data.access_token);
})
Now in another component when sending a post request
export default{
methods:{
getvals(){
localstorage.getItem('access-token') //do stuff after retrieve
}
}
}
Thats just one example, Imagine what could happen when setting multiple localstorage items when retrieving one can type the wrong key.
How can i centralize functionality eg: setting token(in angular2 would be services)
There are a few different ways to share functionality between components in Vue, but I believe the most commonly used are either mixins or custom modules.
Mixins
Mixins are a way to define reusable functionality that can be injected into the component utilizing the mixin. Below is a simple example from the official Vue documentation:
// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
Custom module
If there are a lot of shared functionality with a logical grouping it might make sense to instead create a custom module, and import that where you need it (like how you inject a service in angular).
// localStorageHandler.js
const localStorageHandler = {
setToken (token) {
localStorage.setItem('access-token', token)
},
getToken () {
localstorage.getItem('access-token')
}
}
export default localStorageHandler
And then in your component:
// yourcomponent.vue
import localStorageHandler from 'localStorageHandler'
export default{
methods:{
getvals(){
const token = localStorageHandler.getToken()
}
}
}
Modules are using the more modern syntax of JavaScript, which is not supported in all browsers, hence require you to preprocess your code. If you are using the vue-cli webpack template it should work out of the box.

Vue sharing state between sibling components

I probably do not want to use vuex for state management yet as it is probably overkill for now.
I took a look at https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication. I am using single file component so I am not sure where do I define the shared bus such that both components will have reference to it.
var bus = new Vue()
ChildA.Vue
watch: {
sideNav: (newValue) => {
bus.$emit('sideNav-changed', newValue)
}
}
ChildB.Vue
created () {
bus.$on('sideNav-changed', data => {
console.log(`received new value: ${data}`)
// update own copy here
})
}
Parent.Vue
<template>
<childA>
</childA>
<childB>
</childB>
</template>
I want to watch any changes of sideNav on ChildA and emit it to ChildB to update its own value.
Found the answer to it...
I declare it on the main.js
const bus = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
bus : bus
}
}
})
And also change
watch: {
sideNav: (newValue) => {
bus.$emit('sideNav-changed', newValue)
}
}
to
watch: {
sideNav: function (newValue) {
bus.$emit('sideNav-changed', newValue)
}
}
Is this answer any good to you? You can do everything with events, but if you can avoid them, you should. You might not want vuex for now. That's where I am. But you want, right from the start, a store in global scope and reactive pipes. You "declare" the relationship between an element on the page and an item in the store, then basta. Vue takes care of the rest. You don't care about events.
The simplest way to do this would be to just attach it to the window i.e.
window.bus = new Vue()
Then it will be available in all of your components without the need to define a global mixin e.g. this will still work:
watch: {
sideNav(newValue) {
bus.$emit('sideNav-changed', newValue)
}
}