Vue 3 Event Bus with Composition API - javascript

I have setup mitt and trying to dispatch event to another component but I am having hard time because in the setup() method it doesn't have this for accessing app instance.
Here is what I tried:
import App from './App.vue'
const el = document.getElementById('app')
import mitt from 'mitt';
const emitter = mitt();
const app = createApp(App)
app.config.globalProperties.emitter = emitter;
app.mount(el);
And in the component, I want to dispatch an event
export default {
setup() {
function toggleSidebar() {
this.emitter.emit('toggle-sidebar');
console.log(this); // binds to setup(), not the vue instance.
}
}
}
As this doesn't exist, I can't access the .emitter. What am I missing? How to use officially suggested mitt in Vue 3 composition api?
By the way if I use the v2 syntax, I can access this.emitter. But I am curious about Composition API way
export default {
mounted() {
console.log(this.emitter); // works
}
}

To use an event bus in Vue 3 Composition API, use Vue 3's new provide api in main.js, and then inject in any component:
1. Install mitt:
npm install mitt
2. Provide:
main.js
import { createApp } from 'vue';
import App from './App.vue';
import mitt from 'mitt'; // Import mitt
const emitter = mitt(); // Initialize mitt
const app = createApp(App);
app.provide('emitter', emitter); // ✅ Provide as `emitter`
app.mount('#app');
3. Inject
3a. Any Component - Emit an event
import { inject } from 'vue'
export default {
setup() {
const emitter = inject('emitter'); // Inject `emitter`
const mymethod = () => {
emitter.emit('myevent', 100);
};
return {
mymethod
}
}
}
Call mymethod from a button click or something.
3b. Any Component - Listen for the event
import { inject } from 'vue'
export default {
setup() {
const emitter = inject('emitter'); // Inject `emitter`
emitter.on('myevent', (value) => { // *Listen* for event
console.log('myevent received!', `value: ${value}`);
});
},
}
Console
myevent received! value: 100

You may be able to use getCurrentInstance to get the global property
component:
import { getCurrentInstance } from 'vue';
export default {
setup() {
// get current instance
const internalInstance = getCurrentInstance();
// get the emitter from the instance
const emitter = internalInstance.appContext.config.globalProperties.emitter;
}
}

So far I have used this code to make the "emitter" available.
//main.ts
import mitt from 'mitt'
const emitter = mitt()
export default emitter
And then inside the components I use
import emitter from '#/main';
This worked so far in Vue2 and Vue3 - at least with the options API.
I have to admit though that I currently run into some trouble with the new vite server and the hot module reload (hmr).
Is this style suboptimal in any way?

Related

VueJS define global function for child components

Can someone help me out configuring a global function that I can call in all my Vue files?
When there is this in my Vue file:
#click="ModalShow.show('my-create')"
In the app.js I defined this constant:
const Modals = {
show(screen) {
alert(screen);
// other logic that i implement that should be triggered
},
};
But I keep getting:
TypeError: undefined is not an object (evaluating '_ctx.Modals.show')
What am I missing? It's a Vue project with the composition API
You can use provide/inject, first provide your function to your child components from the app (or parent component)
const Modal = {...}
const app = createApp({})
app.provide('Modal', Modal)
Then inject it into your component
import { inject } from 'vue'
export default {
setup() {
const Modal = inject('Modal')
return { Modal }
}
}
Or via script setup:
<script setup>
import { inject } from "vue";
const Modal = inject("Modal");
</script>

Vue3 | Pinia - Watching storeToRefs in composable function does not work

I'm trying to understand the purpose of composables. I have a simple composable like this and was trying to watch state from a Pinia store where the watch does not trigger:
import { ref, watch, computed } from "vue";
import { storeToRefs } from "pinia";
import useFlightsStore from "src/pinia/flights.js";
import usePassengersStore from "src/pinia/passengers.js";
export function useFlight() {
const route = useRoute();
const modalStore = useModalStore();
const flightsStore = useFlightsStore();
const { selection } = storeToRefs(flightsStore);
const passengersStore = usePassengersStore();
const { passengers, adults, children, infants } =
storeToRefs(passengersStore);
watch([adults, children, infants], (val) => console.log('value changes', val))
Where as the same thing in a Vue component works as expected.
So we cannot watch values inside composables?
I think you can watch values inside composables.
But, to watch a pinia state it has to be inside an arrow function:
watch(() => somePiniaState, (n) => console.log(n, " value changed"));
It's like watching a reactive object.
I believe this should be documented better. In Pinia documentation we can read how to watch the whole store or how to subscribe to a store but not how to watch a single state property inside a component or composable.
Also, the docs are somewhat shy in explaining that you can watch a property inside a store using setup() way of describing a store.
More on this here:
https://github.com/vuejs/pinia/discussions/794#discussioncomment-1643242
This error also silently fails (or does not execute), which is not helpful...
I needed to watch a specific state attribute in one of my components but I didn't find my use case on the official documentation.
I used a mix between a watch and storeToRefs to do it.
import { usePlaylistsStore } from '#/stores/playlists'
import { storeToRefs } from 'pinia'
export default {
name: 'PlaylistDetail',
setup() {
const playlistsStore = usePlaylistsStore()
const { selectedGenres } = storeToRefs(playlistsStore)
return { selectedGenres }
},
watch: {
selectedGenres(newValue, oldValue) {
// do something
}
}
}

cannot import electron in react componment for ipcRenderer

So I am trying to import the ipcRenderer into a react component to communicate with the electron side. The issue is I cannot import electron. I tried
import { ipcRenderer } from 'electron/renderer'
returns module electron/renderer not found
import { ipcRenderer } from 'electron'
returns fs.existsSync is not a function
const renderer = require('electron');
returns fs.existsSync is not a function
const renderer = require('electron').ipcRenderer;
returns fs.existsSync is not a function
const renderer = window.require('electron');
returns window.require is not a function
I do not know what to do anymore, I have tried everything
I got it! using electron-react-bolierplate they prepared a custom preload.js script that exposes three functions to the rendered components: myPing: (Just a demo, send a ping message to the console) and exposes the on and once ipcRenderer methods
import { Component } from 'react';
...
...
class Hello extends Component {
componentDidMount() {
console.log('Mounted!', 'Window', window, 'electronApi', window.electron, 'ipcRenderer', window.electron.ipcRenderer);
window.electron.ipcRenderer.on('ipc-example', (arg) => {
// eslint-disable-next-line no-console
console.log(arg);
});
window.electron.ipcRenderer.myPing();
}
render() {
return (
<div>
...
...
I used to have same problem. You should not import electron directly inside your renderer/react component. Instead in your preload.ts file you are given some basic configuration by electron-react-bolierplate to use.
So inside your react component you should use window.electron.ipcRenderer.on('channel-name', args) sample below
const myEventHandler = () => {
window.electron.ipcRenderer.on('channel-name', (event, data) => {
console.log('data, event', data, event);
});
};
window.electron, here electron is the name given in preload.ts file. contextBridge.exposeInMainWorld('electron', {...})

How to use Vue.prototype or global variable in Vue 3?

Like the title, I want to add Axios into Vue prototype. So when I want to use it, I can use it like this.$axios instead of importing it every time.
CODE:
//plugins/axios.ts
import axios from 'axios'
import router from '../router/index'
const errorHandle = (): void => {};
const instance = axios.create({
// baseURL: process.env.NODE_ENV == 'development' ? '' : ''
baseURL: 'http://localhost:3000',
timeout: 1000 * 12
});
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
export default instance
import { createApp } from 'vue'
import App from './App.vue'
import installElementPlus from './plugins/element'
import instance from './plugins/axios'
import router from './router'
const app = createApp(App).use(router)
installElementPlus(app)
// app.config.globalProperties.http = instance
app.config.globalProperties.$axios= instance
app.mount('#app')
However, there is a problem when I try to use it like this: this.$axios.
TS2339: Property '$axios' does not exist on type 'ComponentPublicInstance<{}, {}, {}, {}, {}, EmitsOptions, {}, {}, false, ComponentOptionsBase<{}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, string, {}>>'.
How could I fix this problem?
You could use provide/inject or define a global config property, which replaces Vue.prototype in Vue 3:
1. provide/inject (for Composition or Options API)
Provide
import axios from 'axios';
const app = Vue.createApp(App);
app.provide('$axios', axios); // Providing to all components during app creation
Inject
Composition API:
const { inject } = Vue;
...
setup() {
const $axios = inject('$axios'); // injecting in a component that wants it
// $axios.get(...)
}
Options API:
inject: ['$axios'], // injecting in a component that wants it
2. Global config (for Options API)
import axios from 'axios';
const app = Vue.createApp(App);
app.config.globalProperties.$axios = axios;
Options API:
this.$axios
Note: This is only for the Options API. Evan You (Vue creator) says: "config.globalProperties are meant as an escape hatch for replicating the behavior of Vue.prototype. In setup functions, simply import what you need or explicitly use provide/inject to expose properties to app."
There is a way to use globalProperties inside a setup function. Although this might be considered an anti-patter. It would be better to use provide/inject if possible. But if there is a library that uses globalProperties and you really need to access it from setup this is how you could do it.
<script setup>
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const progressBar = app.appContext.config.globalProperties.$Progress
progressBar.start()
</script>

Export custom javascript file to a Vue component

I am a beginner in Vue.js and so this question might be duplicate or naive. I want to call functions defined in a custom javascript file within a Vue component. I did something like this.
custom.js
class API{
function testCall(){
alert("test ok");
}
}
export {API}
App.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<testcomponent :on-click="getData">
</testcomponent>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import TestComponent from './components/TestComponent.vue';
import API from './js/custom.js';
export default {
name: 'app',
components: {
HelloWorld,
TestComponent,
API
},
methods: {
getData(){
const apiObj = new API();
apiObj.testCall();
}
}
}
</script>
When I build using npm run build, I get below error.
Any help with this please?
1: To define methods in a class you do not need function keyword.
class API{
testCall(){
alert("test ok");
}
}
2: Since you are doing a named export using export {API}, your import statement should be
import {API} from './js/custom.js';
3:components options is for registering vue components locally. Since API is not a vue component remove it from the components option.
API is not a Vue component - you should not include it inside the components branch. Also, if this is just a bunch of utility functions you can either export them one by one or as a containing object
// util.js - individual functions
export function testCall (call) {};
export function testUser (user) {};
// Vue app
import { testCall, testUser } from 'util.js';
// util.js - object group
function testCall (call)
{
}
function testUser (user)
{
}
export default
{
testCall,
testUser
}
// Vue app
import API from 'util.js';

Categories

Resources