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();
Related
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.
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.
app.html
<script defer src='app.js'></script>
<script defer src='plugin.js'></script>
<div id='app'></div>
<div id='plugin'></div>
app.js
const store = new Vuex.Store({
state:{
hi:'hi'
}
})
const app = new.Vue({
el:'#app',
store
})
plugin.js
const plugin = new.Vue({
mounted(){
console.log('How can I get hi in store?')
}
})
I want to use Vuex to retrieve data from Vue instances loaded from different files. I have to use this method because the plugins are different for each page and they load dynamically.
But rootState doesn't seem to be able to get app's state, because the root is different. Is there a way to access the data using windows, mixins or some other global object or method?
If you want 'shared state' you can use localStorage, sure, but your question asks more about 'passing' data. In this case you can use an event emitter and listener. Since window has an Event Api you can do it like this:
// rootA - emitter
{
methods: {
emitDataToComponentB (a, b, c) {
const event = new CustomEvent('someEvent', { ...arguments })
window.dispatchEvent(event)
}
}
}
// rootB - listener
{
created () {
this.listen()
},
methods: {
listen () {
window.addEventListener('someEvent', (args) => {
console.log('#someEvent', args)
}
}
}
note: Only code required to meet the need shown. You'd want to make sure you detach any listeners at an appropriate time (eg rootB.beforeDestroy())
I would like to implement this kind of logic that assign this.$store.value to local data.
This is how I do in pages/index.vue for instance.
method: {
this.value = this.$store.value
}
I want to write it down into mixins because I actually have another logics around it and I use some pages.
However, I don't know how should I access this(VueInstnce) from mixins?
It is not supported by Vue because mixin runs first before component's code,
then mixin is bound (merged) by Vue to the component instance so it's easy to access mixin from component/instance scope, but not vice versa.
To achieve your need I think the mixin method (like created) should be run (for example) with a given reference to the instance of your component as a parameter, but it's not like that.
However, if you reorganize your code to run what you need from instance.created
accessing there methods and data of mixin is possible and passing arguments on your own:
var mixin = {
data: {mixin: 'mixin'},
created: function () {
console.log('mixin hook called')
},
methods: { test: function(arg){console.log(arg); } }
};
vm=new Vue({
data: {component: 'component'},
mixins: [mixin],
created: function () {
console.log('called hook of ' + this.component + ' and accessing ' + this.mixin)
},
});
vm.test(vm.mixin);
vm.test(vm.component); // no problem to run mixin's method with component's data
> mixin hook called
> called hook of component and accessing mixin
> mixin
> component
Okay so I don't know if it's considered a bad practice but I have managed to accomplish one-way data transmission without an event bus.
I am using vuejs 3 with composition api. Requirement: all components shall be able to access a global singleton component shown on top of whole app.
plugin.js - here we use the created event in mixin to get a reference of an component instance. In example below I always have just 1 tracker component instance (global popup). If you have a different more complex scenario I would recommend sticking with event bus solution instead..
import Tracker from "#/plugins/ProgressTracker/components/Tracker.vue";
export default {
install(app, params = {}) {
app.component("tracker", Tracker);
let instance = undefined;
app.mixin({
created() {
if (this.$options.name === "ProgressTrackerPopup") {
instance = this;
}
},
});
const progressTracker = () => {
//
};
progressTracker.show = function () {
instance.show();
};
app.config.globalProperties.$progressTracker = progressTracker;
},
};
useProgressTracker.js - globally reusable composable function that exposes show method
import { ref, computed, getCurrentInstance } from "vue";
export default function useProgressTracker() {
const internalInstance = getCurrentInstance();
const progressTracker = internalInstance.appContext.config.globalProperties.$progressTracker;
const show = () => {
progressTracker.show();
};
return {
show,
};
}
Tracker.vue - component that we need to globally access from any other component (methods of it).. name is important. It shall be set in order for the mixin to be able to detect your component creation
<template>
<div class="onTop" v-if="isShow">test</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "ProgressTrackerPopup",
setup() {
var isShow = ref(false);
const show = () => {
isShow.value = true;
};
return {
isShow,
show,
};
},
};
</script>
<style scoped>
.onTop{
position: absolute;
z-index: 1999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #0000004f;
}
</style>
this is it. Don't forget to register the plugin:
import ProgressTracker from "#/plugins/plugin.js";
// ..
app.use(ProgressTracker, {});
Now when you want the pop-up to be shown you invoke:
// <tracker />
import useProgressTracker from "#/plugins/ProgressTracker/use/useProgressTracker.js";
const tracker = useProgressTracker();
tracker.show();
The last line of code will basically invoke the show method on global component instance itself! Whereas if you used an event bus instead - you would have subscribed to the pop event on target component itself.
I find this solution to be useful when you don't want to deal with an event bus and the case is relatively trivial (you only have 1 global instance at all times). Though, you could of course use an array of instances and loop-invoke the methods on them in sequence.. :)
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>