How to fire an $emit event from Vue Composable
Asked Answered
G

5

6

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.

Gladysglagolitic answered 26/5, 2022 at 16:18 Comment(0)
G
7

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>
Gargan answered 31/8, 2022 at 11:18 Comment(2)
Do you know if it can be used in script setup? I couldn't make itBritton
I mean <script setup>Britton
D
6

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')
}
Dixon answered 18/11, 2022 at 23:19 Comment(3)
Do you have an idea, how to type emit arg in a composable function?Irving
@AlexanderKim You can type the emit arg by using the ComponentPublicInstance interface. import { ComponentPublicInstance } from 'vue'; type EmitFn = ComponentPublicInstance['$emit'];Mcdavid
This works, yet the composable needs to be aware of the event that the component emits, so they are coupled because of this. Losing kind of the point of having the logic enclosed in the composable. The component would need to emit outside of the composable using strategies like watch, wrapping the emit in a function, etc.Defrost
E
1

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

Emmert answered 26/5, 2022 at 16:41 Comment(1)
The updateState event reinitializes the app state. I need this done under certain conditions so I put it in eventListener that listens for certain key combo and added it to the composable. Then when needed fire the event => call initVars from parent component. to keep things. DRY...Gladysglagolitic
V
1

I found a comprehensive overview of 3 methods of emitting at this link: https://weekly-vue.news/issues/105 These methods are:

  1. Emit from the component - not from a composable.
  2. Pass emit as a parameter
  3. Use getCurrentInstance

Method 3 is discouraged in the docs.

Method 1 is most readable and straightforward, but too long for my purposes.

I used method 2 so that my component makes it clear which event it emits:

// in the component: 

const emit = defineEmits(['delete']);
const { openConfirmDelete } = useConfirmDelete(emit);
// in the composable:

type Emit = (event: 'delete', ...args: any[]) => void;

export function useConfirmDelete(emit: Emit) {
// function body
emit('delete', args);
// etc.
}

Viand answered 9/5 at 16:46 Comment(0)
L
0

If you use TypeScript and don't want to mess around with emit types in composable, you can just pass a callback, and call emit in parent component:

    const emit = defineEmit(['error'];
    function onError() {
        emit('error');
    }
    useMyComposable(onError);
    
    function useMyComposable(onError: () => void){
        onError();
    }
Librarianship answered 21/6 at 4:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.