Vue js send data between two components NO props - vue.js

Hello I'm trying to find a way to send some boolean value from component A to component B without been nested between each other, without props, just send data, one-way binding.
export default {
data: function {
return {
value1: false,
value2:true
}
}
}

Introducing global state with Vuex is probably the best way to go about this.
Without introducing something new into the system, you can handle this with an event bus. Introducing side channel stuff like this does add complexity to your app but is sometimes necessary.
Then in your components you use them like this
// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();
// To setup your component to listen and update based on new value
import { EventBus } from './eventBus';
mounted() {
EventBus.$on('newValue', (val) => this.something = val);
}
// To send out a new value
EventBus.$emit('newValue', 5);

Related

Issue using Socket.io with Pinia

I'm trying to use Pinia to manage some global state in a Vue.js app I'm building, specifically I want to share a Socket.io instance between various components and views. However I'm getting a
this.socketObject.emit is not a function
error when calling functions from the Socket.io instance, when I call them from a component other than the component/view the Socket.io instance was created in. Here's some excerpts of the code.
#/views/LobbyView.vue (This is where I create the Socket.io instance and pass it to the Pinia store, I can use emit fine in this file without any errors)
import io from "socket.io-client";
import { useSocket} from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
async mounted() {
this.socketObject = await io("http://localhost:8000");
this.socketObject.emit("createNewRoom");
}
#/store/index.js Pinia store
import { defineStore } from "pinia";
...
export const useSocket = defineStore({
id: "socket",
state: () => {
return {socketObject: Object};
},
getters: {},
actions: {},
});
#/components/lobbySettings (this is the file where I have issues using Socket.io in via my Pinia store)
import { useSocket } from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
methods: {
startGame() {
this.socketObject.emit("updateRoom", this.roomInfo);
},
},
When the start game method is called on a button press, if I catch the error I get
this.socketObject.emit is not a function
I don't quite understand why Pinia isn't giving me access to functions from my Socket.io instance, the store seems to be working fine for other data in my app, just cant call these functions.
useSocket returns a store, not socket instance. It should be used as:
const socketStore = useSocket();
...
socketStore.socketObject.emit(...)
io(...) doesn't return a promise, it's semantically incorrect to use it with await.
The use of Object constructor is incorrect. If a value is uninitialized, it can be null:
state: () => {
return {socketObject: null};
},
The mutation of store state outside the store is a bad practice. All state modifications should be performed by actions, this way they can be easily tracked through devtools, this is one of benefits of using a store.
At this point there's no benefit from packing socketObject inside a store. Socket instance could be either used separately from a store, or socket instance could be abstracted away and made reactive with store actions, etc.

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.

Simple Vue store pattern - initial server fetch not reacting

This app isn't complicated. I'm trying to create a simple store (not keen to use Vuex for something this light) which should coordinate server requests and make sure there's a single source of truth across the app.
store.js
import Vue from "vue"
import axios from "axios"
class Store {
items = []
constructor() {
this.fetchData()
}
fetchData() {
axios
.get("/api/items")
.then(response => this.fillFieldsFromServer(response.data))
}
fillFieldsFromServer(data) {
// NONE OF THESE WORK
// 1: this.items = data
// 2: this.items = this.items.concat(data)
// 3: Array.prototype.push.apply(this.items, data)
}
}
export const itemStore = Vue.observable(new Store())
component.vue
<template>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<script>
import { itemStore } from "../../stores/item-store.js"
export default {
computed: {
items() {
return itemStore.items
},
},
}
</script>
Obviously I'm fundamentally misunderstanding something here.
What I thought would happen:
The store singleton is created
A server request is fired off
Vue makes the store singleton reactive
The component renders with an empty list
The component watches store.items
The server request returns
The store updates items
The component sees that changes
The component re-renders with the server data
But what's actually happening is that step (8) doesn't occur. The server request returns fine, but the component doesn't see the change so it doesn't re-render.
Obviously I'm doing something wrong. But what?
Vue.observable makes an object reactive by recursively replacing existing properties with get/set accessors, this allows to detect when they are changed. As for arrays, Array.prototype methods that mutate existing array are also replaced to track their calls.
This isn't supposed to work because Array.prototype.push.apply !== store.items.push:
Array.prototype.push.apply(this.items, data)
It should be either:
fillFieldsFromServer(data) {
this.items = data;
}
Or:
fillFieldsFromServer(data) {
this.items.push(...data);
}
Here is a demo.

where to store global object in quasar framework

I'm re-writing my old app using Quasar Framework which is based on Vue, and I have a piece of code (class) which encapsulates websocket functionality.
It is a fairly simple concept: user travels from page to page in the app, but if he receives a message he can see a toast message/reply or a counter of unread messages increments.
I'm a little bit lost in the Quasar (Vue) architecture and here is my question:
Where would I store a global object which communicates with outside world, exists as long as the application exists and accessible from anywhere?
I read documentation of Quasar (Vue) but I still don't know where to put it. Vuex doesn't look right since it is not a state of the app. It is more like a faceless component.
Does it mean that I should use a plugin or Vue.prototype or a global mixin or something else?
I appreciate if someone can share their experience and a piece of code describing how to initialize and access this object's methods.
in my opinion:
Method 1. Use quasar plugin base on Vue prototype (sure you knew it):
plugins/foo.js
const fooModule = {
a: 1,
doTest() { console.log('doTest') }
};
export default ({Vue}) => {
Vue.prototype.$foo = fooModule;
}
quasar.conf.js
plugins: [
'i18n',
'axios',
'foo',
],
component bar.vue:
methods: {
test () { this.$foo.doTest() }
}
Method 2. Just use js module
Because js module is singleton. Wherever you import a js module, it all points to the same pointer.
So just have GlobalTest.js:
export default {
a: 1,
inc() { this.a = this.a + 1 }
}
And test1.js:
import GlobalTest from '/path/to/GlobalTest'
console.log(GlobalTest.a); // output 1
console.log(GlobalTest.inc()); // inc
And test2.js:
import GlobalTest from '/path/to/GlobalTest'
console.log(GlobalTest.a); // Assuming this was called after test1.js: output 2
I used quasar cli but I just consider quasar as a UI lib.
--- Updated ---
It is a fairly simple concept: user travels from page to page in the app, but if he receives a message he can see a toast message/reply or a counter of unread messages increments.
Depend on the requirements, If you need "reactive" you should use Vuex (best built-in reactive lib) + split the app state into modules,
but I only use Vuex when I need "reactive" and avoid it when I just need to read & write the value.
// ~/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let store
export default function (/* { ssrContext } */) {
/* eslint-disable no-return-assign */
return store = new Vuex.Store({
modules: {...},
strict: process.env.DEV
})
}
export function ensureClientStoreExists () {
if (process.env.SERVER) {
return new Promise(() => { /* won't resolve */ })
}
if (process.env.CLIENT) {
if (store) {
return Promise.resolve(store)
}
return new Promise(resolve => {
setTimeout(resolve) // Avoid 'Maximum call stack size exceeded'
}).then(ensureClientStoreExists)
}
}
// Anywhere
import { ensureClientStoreExists } from '~/store/'
ensureClientStoreExists().then(store => {
console.log(store.state)
store.dispatch('XXX/YYY')
})

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.